Почему результат отличается в этих двух последовательностях выражений erlang в оболочке?

В оболочке Erlang, почему следующее дает другой результат?

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


person Greg    schedule 05.10.2017    source источник


Ответы (3)


В Erlang оператор = является одновременно присваиванием и утверждением.

Если я сделаю это:

A = 1,
A = 2,

моя программа выйдет из строя. Я только что сказал ему, что A = 1, который, когда A не привязан (еще не существует в виде метки), ему теперь присваивается значение 1 навсегда и навсегда - до тех пор, пока объем выполнения не изменится. Итак, когда я говорю ему, что A = 2, он пытается утверждать, что значение A равно 2, но это не так. Таким образом, мы получаем вылет из-за плохого матча.

Область видимости в Erlang определяется двумя вещами:

  • Определение текущей функции. Эта область действия является абсолютной на время определения функции.
  • Определение текущего понимания лямбда или списка. Эта область является локальной для лямбда-выражения, но также закрывает любые значения из внешней области, на которые имеются ссылки.

Эти области всегда заменяются в момент объявления тем, что находится во внешней области. Так мы закрываем анонимные функции. Например, предположим, что у меня есть сокет, через который я хочу отправить список данных. Сокет уже привязан к имени переменной Socket в заголовке функции, и мы хотим использовать операцию со списком для сопоставления списка значений для отправки с побочным эффектом отправки через этот конкретный сокет. Я могу закрыть значение сокета в теле лямбды, что имеет эффект каррирования этого значения из более общей операции «отправки некоторых данных»:

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

Каждая итерация операции со списком lists:foreach/2 может принимать только функцию арности 1 в качестве своего первого аргумента. Мы создали замыкание, которое уже внутренне фиксирует значение Socket (потому что оно уже было связано во внешней области) и объединяет его с несвязанной внутренней переменной Message. Также обратите внимание, что мы проверяем, работает ли gen_tcp:send/2 каждый раз в лямбда-выражении, утверждая, что возвращаемое значение gen_tcp:send/2 было действительно ok.

Это очень полезное свойство.

Имея это в виду, давайте посмотрим на ваш код:

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

В приведенном выше коде вы только что присвоили значение 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, использованная там, была полностью предоставлена к неизменяемому пространству определения новой функции представляет присваивание 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 для сравнения, но правила области видимости настолько безумны, что даже не имеет значения ...)

person zxq9    schedule 05.10.2017

В первом случае вы связали Total с 15. В Erlang переменные неизменяемы, но в оболочке, когда вы пишете Total = 15., вы на самом деле не создаете переменную Total, оболочка делает все возможное, чтобы имитировать поведение, которое вы бы имели, если бы вы были запускает приложение и сохраняет в таблице пару {"Total",15}.

В следующей строке вы определяете веселье Calculate. синтаксический анализатор находит выражение Total=2*Number и просматривает его таблицу, чтобы определить, что Total было определено ранее. Оценка превращается в нечто эквивалентное 15 = 2*Number.

Итак, в третьей строке, когда вы запрашиваете оценку Calculate(6), она переходит к вычислению и оценке 15 = 2*6 и выдает сообщение об ошибке.

ошибка исключения: нет совпадения с правым значением 12

Во втором примере Total еще не определен, когда вы определяете функцию. Функция сохраняется без присваивания (Total больше не используется), по крайней мере, без присваивания глобальной переменной. Таким образом, при определении Total не возникает конфликта, а при вычислении Calculate(6). не возникает ошибок.

В скомпилированном модуле поведение будет точно таким же.

person Pascal    schedule 05.10.2017
comment
Думаю, нужно добавить некоторую информацию о сфере действия (контексте), поскольку это делает ответ более ясным. Таблица поиска не всегда проста для понимания. Некоторые ссылки: Learnyousomeerlang.com/higher-order-functions, erlang.org/course/advanced#scope, icai.ektf.hu/pdf/ICAI2007-vol2-pp137-145.pdf - person ; 05.10.2017
comment
@Atomic_alarm: Боюсь, вы правы: o) На самом деле я упоминаю эту таблицу (словарь процессов, в процессе, работающем параллельно с оболочкой), потому что небольшие различия между поведением оболочки и кодом модуля всегда беспокоили меня, особенно тот факт, что можно забыть переменную в оболочке: f (Foo). Но это правильно, это не помогает понять мой ответ, я даже счел необходимым упомянуть, что внутри модуля он работает так же ... - person Pascal; 05.10.2017

Переменной Total уже присвоено значение 15, поэтому вы НЕ можете использовать то же имя переменной Total во второй строке. Вам следует изменить на другое имя Total1 или Total2 ...

person bxdoan    schedule 16.10.2017