I can understand this:
iex(7)> outside_val = 5
5
iex(8)> print = fn() -> IO.puts(outside_val) end
#Function<20.90072148/0 in :erl_eval.expr/5>
iex(9)> print.()
5
:ok
What I don't get so much is why is it Elixir allows the print function to be defined even if outside_val is not defined and only error out later? There is no way to pass in 'outside_val' after the closure has been defined anyway, so isn't it better for Elixir to check for existence of variable during creation ?
What I mean is this:
iex(2)> print = fn () -> IO.puts(outside_val) end
#Function<20.90072148/0 in :erl_eval.expr/5>
iex(3)> outside_val = 5
5
iex(4)> print.()
** (RuntimeError) undefined function: outside_val/0
This is a bug in Elixir which will be fixed in v1.1 (already in the master branch):
Interactive Elixir (1.1.0-dev) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> print = fn () -> IO.puts(outside_val) end
** (CompileError) iex:1: undefined function outside_val/0
The current implementation delays the expansion to invoke the function in IEx.Helpers
. In master, we simply import IEx.Helpers
, so we no longer need to expand outside_val
later.
There is couple of steps, when defining a function in Erlang (and in Elixir, since it is built on top of ErlangVM).
Firstly, you tokenize the input:
{ok, Ts, _} = erl_scan:string("fun() -> Z + 1 end.").
Then, you create the abstract syntax tree:
{ok, [ListAST]} = erl_parse:parse_exprs(Ts).
Last step is to evaluate it with:
Bindings = [{'Z', 1}].
erl_eval:expr(ListAST, Bindings).
In the last step, Erlang can see, that there are undefined variables and raise an exception.
In Elixir most language features are implemented as macros, so the last step is not taken during function definition, but when it is called. I am not sure, if you are able to check, if all variables are bound inside macro definition. If it is possible - that would be cool solution.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With