Clojure позволяет использовать несколько привязок с одним и тем же именем

Я пытаюсь понять некоторое поведение, которое я заметил в Clojure.

Можно создать привязку let с одним и тем же именем привязки, повторяющимся несколько раз:

(let [a 1 a 2 a b] a)
; (= a 2)

(let [a 1 a 2 a 3] a)
; (= a 3)

Я понимаю, что пусть привязки оцениваются, и это все в основном имеет смысл.

Насколько я понимаю из документов, «Локальные значения, созданные с помощью let, не являются переменными. После создания их значения никогда не меняются!»

Действительно ли приведенный выше синтаксис изменяет значение привязок?

Такое ощущение, что это должно вызвать ошибку.

Как своего рода примечание:

Интересно, что вы можете вывести это как JS с помощью clojurescript:

var a__36584 = 1, b__36585 = 2, a__36586 = b__36585;
var a__30671 = 1, a__30672 = 2, a__30673 = 3;

Здесь мы видим, что все значения на самом деле являются отдельными переменными, что указывает на то, что происходит под прикрытием, но некоторые пояснения были бы очень полезны.


person Toby Hede    schedule 28.03.2012    source источник


Ответы (3)


(let [a 1, a 2] a) функционально эквивалентен (let [a 1] (let [a 2] a)), что может быть проще для понимания. В последнем случае относительно легко понять, что вы не «модифицируете» значение a, а вводите новую несвязанную переменную с именем a с другим значением. Вы можете увидеть эффект этого с чем-то вроде (let [a 1] (let [a 2] (println a)) a) - он печатает 2, а затем возвращает 1, потому что внешний a никогда не изменяется, а только временно скрывается. (let [a 1, a 2] a) просто вводит значение с именем a, которое сразу выходит за рамки. Конечно, внешний a доступен до тех пор, пока внутренний a не получит значение, так что вы можете сделать что-то вроде (let [a 1, a (inc a)] a).

person amalloy    schedule 28.03.2012

let в clojure ведет себя как let* из Common Lisp, то есть позволяет более поздним привязкам использовать более ранние. В сочетании с повторной привязкой это может быть полезно, например. когда вам нужно удалить некоторые слои данных чистым способом:

(let [a some-vector, a (first a), a (:key a)] a)

И это, конечно, не ошибка. Как вы заметили, эти привязки внутренне влияют на разные переменные. По сути, это неизменность лексических переменных clojure. Из-за этого пересвязывание лексических переменных имеет чистую семантику (последнее связывание «выигрывает»), и нет причин его запрещать.

person Vladimir Matveev    schedule 28.03.2012

В других ответах правильно отмечено, что синтаксис let эффективно создает новые привязки для a, которые скрывают старую привязку.

Следует отметить один интересный дополнительный момент: это может быть очень полезно для оптимизации кода Clojure, когда вы знаете, что значение будет иметь определенный тип, например:

(let [d (double d)]
  ......)

В блоке let d будет преобразовано, а затем использовано как примитивное двойное число, что может существенно ускорить многие математические операции.

person mikera    schedule 28.03.2012