Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why the output differs in these two erlang expression sequence in shell?

In Erlang shell why the following produces different result?

1> Total=15.    
2> Calculate=fun(Number)-> Total=2*Number end.
3> Calculate(6).   

exception error: no match of right hand side value 12

1> Calculate=fun(Number)-> Total=2*Number end.
2> Total=15.
3> Calculate(6). 

12

like image 648
Greg Avatar asked Oct 05 '17 10:10

Greg


1 Answers

In Erlang the = operator is both assignment and assertion.

If I do this:

A = 1,
A = 2,

my program will crash. I just told it that A = 1 which, when A is unbound (doesn't yet exist as a label) it now is assigned the value 1 forever and ever -- until the scope of execution changes. So then when I tell it that A = 2 it tries to assert that the value of A is 2, which it is not. So we get a crash on a bad match.

Scope in Erlang is defined by two things:

  • Definition of the current function. This scope is absolute for the duration of the function definition.
  • Definition of the current lambda or list comprehension. This scope is local to the lambda but also closes over whatever values from the outer scope are referenced.

These scopes are always superceded at the time they are declared by whatever is in the outer scope. That is how we make closures with anonymous functions. For example, let's say I had have a socket I want to send a list of data through. The socket is already bound to the variable name Socket in the head of the function, and we want to use a list operation to map the list of values to send to a side effect of being sent over that specific socket. I can close over the value of the socket within the body of a lambda, which has the effect of currying that value out of the more general operation of "sending some data":

send_stuff(Socket, ListOfMessages) ->
    Send = fun(Message) -> ok = gen_tcp:send(Socket, Message) end,
    lists:foreach(Send, ListOfMessages).

Each iteration of the list operation lists:foreach/2 can only accept a function of arity 1 as its first argument. We have created a closure that captures the value of Socket internally already (because that was already bound in the outer scope) and combines it with the unbound, inner variable Message. Note also that we are checking whether gen_tcp:send/2 worked each time within the lambda by asserting that the return value of gen_tcp:send/2 was really ok.

This is a super useful property.

So with that in mind, let's look at your code:

1> Total = 15.    
2> Calculate = fun(Number)-> Total = 2 * Number end.
3> Calculate(6).

In the code above you've just assigned a value to Total, meaning you have created a label for that value (just like we had assigned Socket in the above example). Then later you are asserting that the value of Total is whatever the result of 2 * Number might be -- which can never be true since Total was an integer so 2 * 7.5 wouldn't cut it either, because the result would be 15.0, not 15.

1> Calculate = fun(Number)-> Total = 2 * Number end.
2> Total = 15.
3> Calculate(6).

In this example, though, you've got an inner variable called Total which does not close over any value declared in the outer scope. Later, you are declaring a label in the outer scope called Total, but by this time the lambda definition on the first line has been converted to an abstract function and the label Total as used there has been completely given over to the immutable space of the new function definition the assignment to Calculate represented. Thus, no conflict.

Consider what happens, for example, with trying to reference an inner value from a list comprehension:

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

This is not what you would expect from, say, Python 2:

>>> a = 2
>>> a
2
>>> [a * b for b in range(1,4)]
[2, 4, 6]
>>> b
3

Incidentally, this has been fixed in 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

(And I would provide a JavaScript example for comparison as well, but the scoping rules there are just so absolutely insane it doesn't even matter...)

like image 156
zxq9 Avatar answered Nov 11 '22 09:11

zxq9