5

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

Greg
  • 369
  • 1
  • 6

3 Answers3

5

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...)

zxq9
  • 13,020
  • 1
  • 43
  • 60
4

In the first case you have bound Total to 15. In Erlang, variable are unmutable, but in the shell when you write Total = 15. you do not really create the variable Total, the shell does its best to mimic the behavior you will have if you were running an application, and it stores in a table the couple {"Total",15}.

On the next line you define the fun Calculate. the Parser find the expression Total=2*Number, and it goes through its table to detect that Total was previously defined. The evaluation is turned into something equivalent to 15 = 2*Number.

So, in the third line, when you ask to evaluate Calculate(6), it goes to calculate and evaluates 15 = 2*6 and issues the error message

exception error: no match of right hand side value 12

In the second example, Total is not yet defined when you define the function. The function is stored without assignment (Total is not used anymore), at least no assignment to a global variable. So there is no conflict when you define Total, and no error when you evaluate Calculate(6).

The behavior would be exactly the same in a compiled module.

Pascal
  • 13,977
  • 2
  • 24
  • 32
  • 1
    I guess need add some info about scope (context) since it's make answer more clear. "Search table" not always easy for understanding. Some links: http://learnyousomeerlang.com/higher-order-functions , http://www.erlang.org/course/advanced#scope , http://icai.ektf.hu/pdf/ICAI2007-vol2-pp137-145.pdf –  Oct 05 '17 at 11:57
  • @Atomic_alarm : I am afraid you are right :o) In fact I mention this table (a process dictionary, in a process working in parallel to the shell) because the little differences between the shell behavior and the module code have always perturbed me, especially the fact that it is possible to "forget" a variable in the shell : f(Foo). But it is correct, it doesn't help to understand my answer, I even felt necessary to mention that it works the same inside a module... – Pascal Oct 05 '17 at 12:52
1

The variable 'Total' is already assigned a value 15, so you can NOT using the same variable name Total in the second line. You should change to the other name Total1 or Total2...

bxdoan
  • 1,349
  • 8
  • 22