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;

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

Ответ 1

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

Ответ 2

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

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

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

Ответ 3

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

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

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

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