Reputation: 12412
I'm learning Elixir and I'm currently dealing with the semantics of captures and partial application of functions.
I'm already familiar with the fn(x)-> x + 1 end
syntax.
I've noticed what I consider a surprising behavior. For example, I've observed that the last expression in this snippet returns false
:
f = &(&1 <> "-X")
# #Function<6.50752066/1 in :erl_eval.expr/5>
is_function f
# true
is_function &(&1 <> "-X")
# true
f == &(&1 <> "-X")
# false
Which I would interpret as meaning that "recreating" the anonymous function &(&1 <> "-X")
on the fly returns a value that does not equal the value stored in f
(according to the ==
semantics).
That would be fine, and this other snippet would confirm my theory:
g = &(String.upcase(&1))
# &String.upcase/1
is_function g
# true
is_function &(String.upcase(&1))
# true
g == &(String.upcase(&1))
# true
There, g
would equal the recreated anonymous function because – maybe – capturing an existing named function is optimized by the compiler, and the same value is returned each time. The return value of the first line in the second snippet seems to confirm the idea that the two cases are treated differently.
I then tried with a "known" function, meaning that it is already in existence:
f = &(&1 <> "-X")
z = &(f.(&1))
#Function<6.50752066/1 in :erl_eval.expr/5>
z == &(f.(&1))
# false
The last statement is false again.
I would have expected &(f.(&1))
to be treated in a similar way to &(String.upcase(&1))
, since both already exist.
What are the semantics of function captures, then?
Upvotes: 3
Views: 1162
Reputation: 11278
You are right about the fact that capturing a named function is optimized, and thus returns the same value each time.
Further down I'll often refer to anonymous functions by "funs" and to module, function and arity of a function by "mfa".
The fact why there is no equality when capturing a well-known anonymous function is related to how that optimization works. When creating a fun from a named function instead of storing the whole anonymous function, the compiler stores the module name, the function name and the arity of the named function. This is not possible with "recapturing" a well-known anonymous function - you have to create a new fun each time.
Disclaimer: this analysis is not based on the deep knowledge of the compiler, but familiarity with the External Term Format (result of calling :erlang.term_to_binary/1
), which is described in http://erlang.org/doc/apps/erts/erl_ext_dist.html.
There are two distinct types for anonymous functions in ETF:
This gives also an insight into how anonymous functions are implemented: each module has a table of all the anonymous functions that occur inside. I'd suspect the compiler is doing some sort of lambda lifting in order to construct such a table. The funs created from named functions are encoded differently - as the mfa and are probably not stored in the fun table.
Upvotes: 6