Вопрос

I am trying to understand some behaviour I have noticed in Clojure.

It is possible to create a let binding with the same binding-name repeated multiple times:

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

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

I understand that let bindings are evaluated, and this all mostly makes sense.

My understanding from the docs is that "Locals created with let are not variables. Once created their values never change!"

Does the above syntax actually change the value of the bindings?

This feels like it should raise an error.

As a sort of side note:

Interestingly you can output the above as JS with clojurescript:

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

Here we can see that the values are all actually distinct variables, which points to what is happening under the covers, but some clarification would be very helpful.

Это было полезно?

Решение

(let [a 1, a 2] a) is functionally equivalent to (let [a 1] (let [a 2] a)), which may be easier to understand. In the latter case, it is relatively easy to realize that you're not "modifying" the value of a, but introducing a new, unrelated variable named a with a different value. You can see the effect of this with something like (let [a 1] (let [a 2] (println a)) a) - it prints 2, and then returns 1, because the outer a is never changed, only temporarily hidden. (let [a 1, a 2] a) is simply introducing a value named a that immediately goes out of scope. Of course, the outer a is available until the inner a has a value, so you can do something like (let [a 1, a (inc a)] a).

Другие советы

let in clojure behaves like let* from Common Lisp, that is, it allows later bindings to use earlier. In conjunction with rebinding this can be useful e.g. when you need to remove some layers of data in a clean way:

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

And of course this is not an error. As you have noticed, these bindings internally affect different variables. This essentially is immutability of clojure lexical variables. Because of this lexical variables rebinding have clean semantics (the last binding "wins"), and there is no reason to disallow it.

Other answers have correctly noted that the let syntax effectively creates new bindings for a that hide the old binding.

One interesting additional point to note is that this can be very useful for optimisation of Clojure code when you know that a value will have a specific type, e.g.:

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

Within the let block, d will be cast then used as a primitive double which can substantially speed up many mathematical operations.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top