Erlang: higher order functions
Erlang is a functional language: it discourage assignments and implements immutability of variables, plus providing the classic set of higher-order functions that these languages are famous for.
Of course functions in Erlang are not limited to named functions definition. It's esy to create functions on the fly, inside other code.
These functions can be used right away:
anonymous_function_test() -> Squared = (fun(N) -> N*N end)(5), ?assertEqual(25, Squared).
or they can be assigned to variables as any other value:
assigned_anonymous_function_test() -> Square = fun(N) -> N*N end, Squared = Square(5), ?assertEqual(25, Squared).
The ability to generate functions is key in implementing logic: an object can be think of as a set of correlated functions, generated over the same data. These functions, referring to already existing variables, are called closures (oversimplified definition, sorry).
To build closures, you can refer freely to variables inside the current scope. Being the variables immutable, you don't have to worry about concurrent modifications or actions at a distance.
closure_currying_test() -> Times = fun(M) -> fun(N) -> M * N end end, FiveTimes = Times(5), ?assertEqual(15, FiveTimes(3)).
The higher-order functions
Higher-order functions are functions that takes as arguments other functions: if you had already wrote 10 lines of code in a functional language, you'll know a few of them. And if you did not live under a rock, you'll know at list map and reduce.
Let's define some functions of the zeroth-order. Named functions and anonymous functions can be interchanged in all the examples that follow.
For a named function, simply pass to the higher-order function the keyword fun followed by the name and the arity of the function:
For an anonymous function, simply use the variable name:
For simplicity I will stay with named functions:
square(N) -> N * N. is_square(N) -> Root = erlang:round(math:sqrt(N)), Root * Root == N.
We can use these named functions as if they were kept into variables, with the right name. Map applies a function to each element of a list, returning a new list with the results:
map_test() -> ?assertEqual([1, 4, 9], lists:map(fun square/1, [1, 2, 3])).
Filter is also a famous example of higher-order functions: it selects the element of a list that satisfy a predicate.
filter_test() -> ?assertEqual(, lists:filter(fun is_square/1, [3, 4, 5])).
A predicate is defined as a function of a single value, which is an element of a list, and that return a boolean.
There are other two functions that take a predicate as an argument, besides the list to work on. all/2 and any/2 return a boolean:
all_test() -> ?assertEqual(true, lists:all(fun is_square/1, [4, 9])), ?assertEqual(false, lists:all(fun is_square/1, [3, 4, 9])). any_test() -> ?assertEqual(true, lists:any(fun is_square/1, [3, 4, 5])), ?assertEqual(false, lists:any(fun is_square/1, [3, 5, 7])).
Which is almost an oxymoron. However, there are more complex constructs too, that you will be glad are already implemented when needing them. Higher-order functions have great reusability:
dropwhile_test() -> ?assertEqual([5, 6], lists:dropwhile(fun is_square/1, [1, 4, 5, 6])). takewhile_test() -> ?assertEqual([1, 4], lists:takewhile(fun is_square/1, [1, 4, 5, 6])).
dropwhile/2 and takewhile/2 walk a list and, respectively, remove or keep the elements satisfying the predicate to produce a new list. Remember, immutability means all these functions always return their result and cannot touch the state of what you pass to them.
Fold (also known as reduce) comes in the foldl/3 (from the left) and foldr/3 (from the right) variants. Its job is to recursively apply a function to pair of elements from a list, starting with a seed (accumulator) that is passed to it and is usually the neutral element for the function.
foldl_test() -> Sum(A, B) -> A + B end, ?assertEqual(10, lists:foldl(Sum, 0, [1, 2, 3, 4])).
Explore more of these functions in the Erlang documentation.
Higher-order functions are the bread and butter of functional languages, although they are one of the simpler aspects of it and not the whole picture. Knowing how to use map and foldl is general Erlang knowledge, but we'll see in the next article that there are even more idiomatic ways to work with lists: list comprehensions.