为什么 Elixir 允许带有未定义变量的闭包?

Why does Elixir allow closures with undefined variables?

我能理解这个:

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

我不太明白的是,为什么 Elixir 允许定义打印函数,即使 outside_val 没有定义,而且只是稍后出错?在定义了闭包之后就没有办法传入 'outside_val' 了,所以 Elixir 在创建过程中检查变量是否存在不是更好吗?

我的意思是:

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

在 Erlang 中(以及在 Elixir 中,因为它建立在 ErlangVM 之上)定义函数时有几个步骤。

首先,您将输入标记化:

{ok, Ts, _} = erl_scan:string("fun() -> Z + 1 end.").

然后,您创建抽象语法树:

{ok, [ListAST]} = erl_parse:parse_exprs(Ts).

最后一步是评估它:

Bindings = [{'Z', 1}].
erl_eval:expr(ListAST, Bindings).

在最后一步,Erlang 可以看到存在未定义的变量并引发异常。

在 Elixir 中,大多数语言特性都是作为宏实现的,所以最后一步不是在函数定义时执行的,而是在调用时执行的。我不确定,如果你能够检查,是否所有变量都绑定在宏定义中。如果可能 - 那将是很酷的解决方案。

这是 Elixir 中的一个错误,将在 v1.1 中修复(已在 master 分支中):

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

当前的实现延迟了扩展以调用 IEx.Helpers 中的函数。在master中我们直接导入IEx.Helpers,后面就不用再展开outside_val