Because anonymous functions are closures, they keep track of the data captured from outside the function definition. In other words, each fun has an associated data table containing an entry for each variable referenced inside the function definition along with the value for that variable.
So when a function is passed to spawn()
, the runtime system knows what it needs to copy into the newly spawned process in order to run the function there.
iex(8)> data = "data"
iex(9)> num = 13
iex(10)> foo = fn -> IO.puts "this is my data: #{inspect(data)} and number: #{inspect(num)}" end
#Function<20.128620087/0 in :erl_eval.expr/5>
iex(11)> :erlang.fun_info foo
[
pid: #PID<0.106.0>,
module: :erl_eval,
new_index: 20,
new_uniq: <<245, 82, 198, 227, 120, 209, 152, 67, 80, 234, 138, 144, 123, 165,
151, 196>>,
index: 20,
uniq: 128620087,
name: :"-expr/5-fun-3-",
arity: 0,
env: [
# The first list below is the data from external variables stored inside
# the closure.
{[_@0: "data", _@2: 13], :none, :none,
[
{:clause, 10, [], [],
[
{:call, 10, {:remote, 10, {:atom, 0, IO}, {:atom, 10, :puts}},
[
{:bin, 10,
[
{:bin_element, 10, {:string, 0, 'this is my data: '}, :default,
:default},
{:bin_element, 10,
{:call, 10,
{:remote, 10, {:atom, 0, Kernel}, {:atom, 10, :inspect}},
[{:var, 10, :_@0}]}, :default, [:binary]},
{:bin_element, 10, {:string, 0, ' and number: '}, :default,
:default},
{:bin_element, 10,
{:call, 10,
{:remote, 10, {:atom, 0, Kernel}, {:atom, 10, :inspect}},
[{:var, 10, :_@2}]}, :default, [:binary]}
]}
]}
]}
]}
],
type: :local
]
Here’s another example:
iex(14)> my_string = "this is a string inside a fun"
iex(15)> bar = fn -> String.reverse(my_string) end
#Function<20.128620087/0 in :erl_eval.expr/5>
iex(16)> :erlang.fun_info bar
[
pid: #PID<0.106.0>,
module: :erl_eval,
new_index: 20,
new_uniq: <<245, 82, 198, 227, 120, 209, 152, 67, 80, 234, 138, 144, 123, 165,
151, 196>>,
index: 20,
uniq: 128620087,
name: :"-expr/5-fun-3-",
arity: 0,
env: [
{[_@3: "this is a string inside a fun"], :none, :none,
[
{:clause, 15, [], [],
[
{:call, 15, {:remote, 15, {:atom, 0, String}, {:atom, 15, :reverse}},
[{:var, 15, :_@3}]}
]}
]}
],
type: :local
]
Compare that to a fun that doesn’t close over any data external to it:
iex(2)> :erlang.fun_info fn -> str = "just a string"; String.reverse(str) end
[
pid: #PID<0.106.0>,
module: :erl_eval,
new_index: 20,
new_uniq: <<245, 82, 198, 227, 120, 209, 152, 67, 80, 234, 138, 144, 123, 165,
151, 196>>,
index: 20,
uniq: 128620087,
name: :"-expr/5-fun-3-",
arity: 0,
env: [
{[], :none, :none,
[
{:clause, 2, [], [],
[
{:match, 2, {:var, 2, :_str@1},
{:bin, 0,
[
{:bin_element, 0, {:string, 0, 'just a string'}, :default,
:default}
]}},
{:call, 2, {:remote, 2, {:atom, 0, String}, {:atom, 2, :reverse}},
[{:var, 2, :_str@1}]}
]}
]}
],
type: :local
]