Funs and higher-order functions
A fun is a function you can hold in a variable, pass to another function and get back. Funs remember the values they were made with, which is what makes map and foldl expressive.
A fun is a value
A fun — fun(X) -> X + X end, for example — is a function without a name. Nothing is called yet: the expression is a value, and like any value it can be bound to a variable, put in a list, passed as an argument or returned from another function. Calling it later looks exactly like calling a named function: Add(2, 3).
A named function of the same module can be referred to as a value too, with fun name/arity. fun shout/1 is the function called shout taking one argument, as a value. The arity appears again, and for the same reason as in an export line: the name alone does not identify a function.
Funs are how work is handed to the library. lists:map does not know what doubling is, and it does not need to: it is given a fun and applies it to each element. The same library call serves every transformation a program needs.
A fun can have several clauses, separated by semicolons, and each clause can match a pattern: fun({ok, V}) -> V; ({error, R}) -> R end. That makes a small dispatch table you can pass around, and the same completeness question applies as in a case: a shape no clause matches raises a function clause error when the fun is called.
Funs remember
A fun closes over the variables that were bound when it was made. The fun bound to Scale carries Factor inside itself, so calling Scale months of code later still uses the same number even though Factor is nowhere in sight.
This is how a general function is turned into a specific one without writing a new clause. Change Factor and the same fun expression makes a different multiplier; there is no global state being read, and nothing about the old fun changes.
Returning a fun from a fun composes them: Both = fun(F, G) -> fun(X) -> F(G(X)) end end builds a new function that applies G first and F to the result. Every value here is a function, and none of them runs until a number arrives.
Funs and named functions
Prefer a named function when the logic has a name worth saying, and a fun when the logic is short enough to read on the spot. A named function can be passed with fun name/1, so there is no penalty for choosing the clearer option — the two mix freely.
Bind a fun, pass a fun, return a fun
-module(prog).
-export([main/1]).
main(_) ->
Add = fun(A, B) -> A + B end,
io:format("~p~n", [Add(2, 3)]),
Factor = 10,
Scale = fun(X) -> X * Factor end,
io:format("~p~n", [lists:map(Scale, [1, 2, 3])]),
Apply = fun(F, X) -> F(X) end,
io:format("~p~n", [Apply(fun(N) -> shout(N) end, 7)]),
Both = fun(F, G) -> fun(X) -> F(G(X)) end end,
Twice = Both(fun(N) -> shout(N) end, Scale),
io:format("~p~n", [Twice(2)]).
shout(N) -> N * 100.Functions as values
Run it and check the last answer by hand: Scale doubles 2, then shout multiplies by a hundred. Then make a fun that adds three numbers and pass it to the Apply helper.
A fun does nothing until it is called. If a run prints a strange function-shaped value instead of a number, the code built the fun and then forgot the parentheses that would apply it.
A closure holds the values it was made with
When a fun is created, it captures the names that are bound at that moment. Make = fun(Factor) -> fun(X) -> X * Factor end end creates a new inner fun each time it is called, and each one carries its own Factor. Calling Make(2) and Make(3) produces two independent multipliers.
The captured value does not change when the original name goes out of scope, and it cannot be changed afterward, because Erlang has no assignment. That is what makes a closure a safe way to turn a general function into a specific one without a global setting.
A fun has an arity, just like a named function
fun(X) -> X * 2 end is a function of one argument; fun add/2 is a reference to a function of two. Applying a fun to the wrong number of arguments raises a badarity error, which is the same rule that makes main/1 and main/0 different names.
A fun can be passed wherever a value is expected, including to another fun. The receiving function does not need to know how the fun was built; it only needs to know how many arguments to supply. That is the whole contract behind lists:map, lists:filter and lists:foldl.
Three fun mistakes are easy to spot once named. Writing fun shout/1 passes the function; writing shout/1 does not call it either, because the slash is only for references. Forgetting the parentheses in F(X) leaves the fun itself as the value. And passing a two-argument fun where a one-argument fun is expected fails with badarity when the library applies it.
Try these next
- Make an
AddNfactory and build adders for 1, 5 and 10. - Pass an inline fun to
lists:map, then write the same transformation as a named function and call it directly. - Compose two funs in the opposite order and check how the answer changes.
Build multipliers and compose them
Run it and check the composed answer by applying the two funs in order by hand. Then build a fun that subtracts one and compose it with the doubler.
Check what you learned
One question at a time. A wrong answer explains itself — take another go.
Part 1 of 5
What does the expression fun(X) -> X + X end produce?
