Here’s some strange code:
| iex> fun1 = fn -> fn -> "Hello" end end |
| #Function<12.17052888 in :erl_eval.expr/5> |
| iex> fun1.() |
| #Function<12.17052888 in :erl_eval.expr/5> |
| iex> fun1.().() |
| "Hello" |
The strange thing is the first line. It’s hard to read, so let’s spread it out.
| fun1 = fn -> |
| fn -> |
| "Hello" |
| end |
| end |
The variable fun1 is bound to a function. That function takes no parameters, and its body is a second function definition. That second function also takes no parameters, and it evaluates the string "Hello".
When we call the outer function (using fun1.()), it returns the inner function. When we call that (fun1.().()) the inner function is evaluated and “Hello” is returned.
We wouldn’t normally write something such as fun1.().(). But we might call the outer function and bind the result to a separate variable. We might also use parentheses to make the inner function more obvious.
| iex> fun1 = fn -> (fn -> "Hello" end) end |
| #Function<12.17052888 in :erl_eval.expr/5> |
| iex> other = fun1.() |
| #Function<12.17052888 in :erl_eval.expr/5> |
| iex> other.() |
| "Hello" |
Let’s take this idea of nesting functions a little further.
| iex> greeter = fn name -> (fn -> "Hello #{name}" end) end |
| #Function<12.17052888 in :erl_eval.expr/5> |
| iex> dave_greeter = greeter.("Dave") |
| #Function<12.17052888 in :erl_eval.expr/5> |
| iex> dave_greeter.() |
| "Hello Dave" |
Now the outer function has a name parameter. Like any parameter, name is available for use throughout the body of the function. In this case, we use it inside the string in the inner function.
When we call the outer function, it returns the inner function definition. It has not yet substituted the name into the string. But when we call the inner function (dave_greeter.()), the substitution takes place and the greeting appears.
But something strange happens here. The inner function uses the outer function’s name parameter. But by the time greeter.("Dave") returns, that outer function has finished executing and the parameter has gone out of scope. And yet when we run the inner function, it uses that parameter’s value.
This works because functions in Elixir automatically carry with them the bindings of variables in the scope in which they are defined. In our example, the variable name is bound in the scope of the outer function. When the inner function is defined, it inherits this scope and carries the binding of name around with it. This is a closure—the scope encloses the bindings of its variables, packaging them into something that can be saved and used later.
Let’s play with this some more.
In the previous example, the outer function took an argument and the inner one did not. Let’s try a different example where both take arguments.
| iex> add_n = fn n -> (fn other -> n + other end) end |
| #Function<12.17052888 in :erl_eval.expr/5> |
| iex> add_two = add_n.(2) |
| #Function<12.17052888 in :erl_eval.expr/5> |
| iex> add_five = add_n.(5) |
| #Function<12.17052888 in :erl_eval.expr/5> |
| iex> add_two.(3) |
| 5 |
| iex> add_five.(7) |
| 12 |
Here the inner function adds the value of its parameter other to the value of the outer function’s parameter n. Each time we call the outer function, we give it a value for n and it returns a function that adds n to its own parameter.