Anonymous Functions
When we write a function such as
double x = 2 * x
which is a function that doubles its argument, we actually do two things in one
go: we define the function that doubles its argument—this function has no
name—and then we assign it to the variable double
so we have a name by which
we can refer to the function. This is completely analogous to assigning the
value of some integer or string expression to a variable.
Functions without names are called anonymous functions or lambda expressions. The above function as an anonymous function is written like this:
\x -> 2 * x
The backslash at the beginning looks a little bit like a \(\lambda\) with its left
leg missing. This is the symbol used in Haskell to introduce an anonymous
function definition. What follows is the list of arguments. Here, there is one
argument, x
. Then we have the arrow, ->
, and finally the value of the
function given this argument, 2 * x
.
The equation
double x = 2 * x
is really just syntactic sugar1 for
double = \x -> 2 * x
We create a function that doubles its argument and bind this function to the
name double
. In mathematics, you sometimes see functions written like this:
The first part says that the function maps integers to integers. The second
part specifies the mapping. It says the same as if we had written
\(\textit{double}(x) = 2x\). If you write the type signature of double
and
its definition,
double :: Int -> Int
double = \x -> 2 * x
you get the Haskell equivalent of these two parts of the function definition.
>>> double = \x -> 2 * x
>>> double 5
10
We can also define anonymous multi-argument functions. The function
\x y -> x * y
takes two arguments, x
and y
, and returns their product. The definition
multiply x y = x * y
is once again just syntactic sugar for
multiply = \x y -> x * y
>>> multiply = \x y -> x * y
>>> multiply 5 3
15
Why do we care about the ability to define anonymous functions? Sometimes we
need to build a custom function that we need once, as an argument to another
function, but nowhere else. In that case, it's useful not to give that
function a name and simply pass it directly to the other function. For
example, we will later discuss the filter
function, which allows us to
extract the list of all elements in a list that meet a certain condition. Say,
I have the list of all positive integers between 1
and 1000000
, written
[1..1000000]
in Haskell. Let's say I want to extract from it all even
integers between 100
and 200
. The filter
function has the following type
(don't worry that we haven't formally talked about lists yet, this should be
fairly easy to understand):
filter :: (a -> Bool) -> [a] -> [a]
Its first argument is a Boolean predicate, a function that checks whether a
value of type a
has a certain property. The second argument is a list of
values of type a
. What filter
returns is the list of all values in this
list that have the desired property, for which the predicate is True
. For
example, we have
>>> even 1
False
>>> even 2
True
>>> filter even [1..10]
[2,4,6,8,10]
Now we don't only want to extract the even numbers but the even numbers between
100
and 200
. To express this condition, we need a predicate that tests
all of these conditions at once. We could do this like this:
>>> select x = even x && x >= 100 && x <= 200
>>> filter select [1..1000000]
[100,102,104,106,108,110,112,114,116,118,120,122,124,126,128,130,132,134,136,138
,140,142,144,146,148,150,152,154,156,158,160,162,164,166,168,170,172,174,176,178
,180,182,184,186,188,190,192,194,196,198,200]
but this is exactly what we would like to avoid—we're defining the function
select
only for use within the filter
expression. This is where anonymous
functions come in handy because we can simply write
>>> filter (\x -> even x && x >= 100 && x <= 200) [1..1000000]
[100,102,104,106,108,110,112,114,116,118,120,122,124,126,128,130,132,134,136,138
,140,142,144,146,148,150,152,154,156,158,160,162,164,166,168,170,172,174,176,178
,180,182,184,186,188,190,192,194,196,198,200]
-
"Syntactic sugar" is a term commonly used to refer to a feature in a language's syntax that does not add a new capability to the language but makes an existing capability more convenient to use. ↩