Lambdas
Sometimes we need to create a function for the sole purpose of passing it off to a higher-order function like map
or filter
or foldr
or foldl
. We could define the helper function at the top-level, as twice
is here:
-- Helper that doubles a single number.
twice :: Int -> Int
twice x = 2 * x
twiceAll :: [Int] -> [Int]
twiceAll xs = map twice xs
This is not ideal if twice
is not needed for anything else. The definition has a wider scope than necessary. Defining the helper in a where
clause narrows its scope:
twiceAll :: [Int] -> [Int]
twiceAll xs = map twice xs
where
twice :: Int -> Int
twice x = 2 * x
But there's an even better solution. Many languages provide lambdas as an alternative means of constructing short-lived functions with narrow scope and without a lot of fanfare. Lambdas are a lot like the blocks we used in Ruby in that they strip a function down to just its essentials: a parameter list and a body. Gone is the name. Gone is the noisy syntax. In Haskell, a lambda that doubles a number looks like this:
\x -> 2 * x
In grammatical terms, a lambda is a backslash, a space-separated parameter list, an arrow, and an expression:
\param1 param2 param3 ... -> expression
This lambda reports if its parameter is the empty list or a list with one element:
\xs -> null xs || length xs == 1
This lambda reports if a number is divisible by 5:
\x -> mod x 5 == 0
This lambda reports if two numbers are within 3 of each other:
\x y -> abs (x - y) <= 3
Pattern matching is legal in the list of formal parameters. This lambda swaps the components of a 2-tuple:
\(a, b) -> (b, a)
We could assign these lambdas to identifiers and then call them as we call any function:
twice = \x -> 2 * x
swap = \(a, b) -> (b, a)
twice 5 -- yields 10
swap (1, 2) -- yields (2, 1)
But assigning them to variables in the current scope is not usually what we do. If we wanted a named function, we would have used the named-function syntax. Instead, lambdas are usually anonymous. They are firecrackers, used for their brief moment of glory and then thrown away.
Suppose we have a list of numbers, and we want to keep only the numbers in the interval [13, 19]. We might define this teens
function, which calls filter
with a lambda predicate:
teens :: [Int] -> [Int]
teens xs = filter (\x -> 13 <= x && x <= 19) xs
Parentheses are needed around the lambda to separate it from surrounding tokens.
Suppose we have a list of number pairs, and we want to turn it into a list of the larger numbers within the pairs. We might define this biggers
function, which calls map
with a lambda transform:
biggers :: [(Int, Int)] -> [Int]
biggers xs = map (\(a, b) -> max a b) xs
biggers [(1, 2), (10, 5)] -- yields [2, 10]
Suppose we have a list of words and we want to pull out their first letters to create an acronym. We might define this acronym
function, which calls foldr
with a lambda mixing function:
acronym :: [String] -> String
acronym words = foldr (\x accum -> head x : accum) "" words
acronym ["higher", "order", "function"] -- yields "hof"
Many other languages support lambdas. JavaScript has syntax similar to Haskell, but uses =>
instead of ->
:
const afters = [1, 2, 3].map(x => x + 1);
console.log(afters); // prints [2, 3, 4]
const positives = [-10, 0, 10].filter(x => x > 0);
console.log(positives); // prints [10]
Java 8 introduced lambdas. These provide an abbreviated syntax for creating anonymous classes that have only a single method. Prior to lambdas, we might have registered a callback on a button with this clumsy code:
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent event) {
window.close();
};
});
A lambda simplifies this code considerably:
button.addActionListener(event -> window.close());
The Java compiler internally translates the lambda into an instance of ActionListener
and inserts the lambda's expression as the body of the actionPerformed
method.
Unlike Haskell, both Java and JavaScript allow the body to be a block of multiple statements instead of a single expression. If a block is used, it must be surrounded by curly braces and have an explicit return statement when returning a value.