在Erlang shell中为什么以下产生不同的结果?
1> Total=15.
2> Calculate=fun(Number)-> Total=2*Number end.
3> Calculate(6).
异常错误:右侧值12匹配
1> Calculate=fun(Number)-> Total=2*Number end.
2> Total=15.
3> Calculate(6).
12
答案 0 :(得分:5)
在Erlang中,=
运算符是赋值和断言。
如果我这样做:
A = 1,
A = 2,
我的程序会崩溃。我刚刚告诉它A = 1
,当A
未绑定(尚未作为标签存在)时,它现在永远被赋予值1 - 直到执行范围发生变化。那么当我告诉它A = 2
它试图断言时A
的值是2,而不是Socket
。所以我们在糟糕的比赛中遇到了崩溃。
Erlang中的范围由两件事来定义:
这些范围在总是取代时被外部范围中的任何内容声明 。这就是我们用匿名函数创建闭包的方法。例如,让我说我有一个套接字我想通过发送数据列表。套接字已绑定到函数头部中的变量名send_stuff(Socket, ListOfMessages) ->
Send = fun(Message) -> ok = gen_tcp:send(Socket, Message) end,
lists:foreach(Send, ListOfMessages).
,我们希望使用列表操作映射值列表以发送到通过该特定套接字发送的副作用。我可以在lambda体内关闭套接字的值,这样可以将该值与#34;发送一些数据"的更一般的操作区分开来:
lists:foreach/2
列表操作Socket
的每次迭代只能接受arity 1的函数作为其第一个参数。我们已经创建了一个闭包,它已经在内部捕获Message
的值(因为它已经绑定在外部作用域中)并将其与未绑定的内部变量gen_tcp:send/2
组合在一起。另请注意,我们通过断言gen_tcp:send/2
的返回值确实 ok
来检查1> Total = 15.
2> Calculate = fun(Number)-> Total = 2 * Number end.
3> Calculate(6).
每次是否在lambda中工作。
这是超级有用的属性。
因此,考虑到这一点,让我们看看你的代码:
Total
在上面的代码中,您刚刚为Socket
分配了一个值,这意味着您已为该值创建了一个标签(就像我们在上面的例子中指定了Total
一样)。然后你断言 2 * Number
的值是Total
可能的结果 - 由于2 * 7.5
是一个整数所以永远不会是真的15.0
也不会将其剪切,因为结果为15
,而不是1> Calculate = fun(Number)-> Total = 2 * Number end.
2> Total = 15.
3> Calculate(6).
。
Total
但是,在此示例中,您有一个名为Total
的内部变量,它不会关闭外部作用域中声明的任何值。稍后, 在外部作用域中声明一个名为Total
的标签,但此时第一行的lambda定义已转换为抽象函数,标签为{{1}如所使用的,已经完全赋予新函数定义的不可变空间对Calculate
所表示的赋值。因此,没有冲突。
考虑一下会发生什么,例如,尝试从列表理解中引用内部值:
1> A = 2.
2
2> [A * B || B <- lists:seq(1,3)].
[2,4,6]
3> A.
2
4> B.
* 1: variable 'B' is unbound
这不是你所期望的,比方说,Python 2:
>>> a = 2
>>> a
2
>>> [a * b for b in range(1,4)]
[2, 4, 6]
>>> b
3
顺便说一句,这在Python 3中得到了修复:
>>> a = 2
>>> a
2
>>> [a * b for b in range(1,4)]
[2, 4, 6]
>>> b
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'b' is not defined
(我会提供一个JavaScript示例进行比较,但是那里的范围规则确实非常疯狂,它甚至不重要......)
答案 1 :(得分:4)
在第一种情况下,你将Total绑定到15.在Erlang中,变量是不可变的,但是当你编写Total = 15.
时你在shell中没有真正创建变量Total
,shell会执行它最好模仿运行应用程序时的行为,并将其存储在表格{"Total",15}
中。
在下一行中,您可以定义乐趣Calculate。解析器找到表达式Total=2*Number
,并通过其表来检测以前定义的总计。评估将变为等同于15 = 2*Number
的内容。
因此,在第三行中,当您要求评估Calculate(6)
时,它会计算并评估15 = 2*6
并发出错误消息
异常错误:右侧值12匹配
在第二个示例中,定义函数时尚未定义Total。该函数存储时没有赋值(Total不再使用),至少没有赋值给全局变量。因此,定义Total时没有冲突,评估Calculate(6).
编译模块中的行为完全相同。
答案 2 :(得分:1)
变量'Total'已经赋值为15,因此您不能在第二行使用相同的变量名Total。您应该更改为其他名称Total1或Total2 ...