72

I understand that they're different since one works for setting *compile-path* and one doesn't. However, I need help with why they're different.

let creates a new scope with the given bindings, but binding...?

Prince John Wesley
  • 62,492
  • 12
  • 87
  • 94
Carl
  • 887
  • 1
  • 7
  • 6

3 Answers3

112

let creates a lexically scoped immutable alias for some value. binding creates a dynamically scoped binding for some Var.

Dynamic binding means that the code inside your binding form and any code which that code calls (even if not in the local lexical scope) will see the new binding.

Given:

user> (def ^:dynamic x 0)
#'user/x

binding actually creates a dynamic binding for a Var but let only shadows the var with a local alias:

user> (binding [x 1] (var-get #'x))
1
user> (let [x 1] (var-get #'x))
0

binding can use qualified names (since it operates on Vars) and let can't:

user> (binding [user/x 1] (var-get #'x))
1
user> (let [user/x 1] (var-get #'x))
; Evaluation aborted.
;; Can't let qualified name: user/x

let-introduced bindings are not mutable. binding-introduced bindings are thread-locally mutable:

user> (binding [x 1] (set! x 2) x)
2
user> (let [x 1] (set! x 2) x)
; Evaluation aborted.
;; Invalid assignment target

Lexical vs. dynamic binding:

user> (defn foo [] (println x))
#'user/foo
user> (binding [x 1] (foo))
1
nil
user> (let [x 1] (foo))
0
nil

See also Vars, let.

ohcibi
  • 2,518
  • 3
  • 24
  • 47
Brian Carper
  • 71,150
  • 28
  • 166
  • 168
  • 5
    This plus http://en.wikipedia.org/wiki/Scope_(programming)#Static_versus_dynamic_scoping really advanced my understanding. Thank you sir! – Carl Oct 06 '09 at 22:21
  • 1
    The x must be bound with the ^:dynamic hint to not throw an error, i beleave. – WeGi Feb 14 '14 at 14:29
13

One more syntactic difference for let vs binding:

For binding, all the initial values are evaluated before any of them are bound to the vars. This is different from let, where you can use the value of a previous "alias" in a subsequent definition.

user=>(let [x 1 y (+ x 1)] (println y))
2
nil

user=>(def y 0)
user=>(binding [x 1 y (+ x 1)] (println y))
1
nil
Prince John Wesley
  • 62,492
  • 12
  • 87
  • 94
Marc
  • 824
  • 10
  • 18
  • You need ^:dynamic to define x to 0 (also dynamically) to make your second example work. – John Feb 21 '14 at 07:38
9

binding binds a value to a name in the per-thread global environment

As you mentioned, let creates a new scope for said bindings.

Yuval Adam
  • 161,610
  • 92
  • 305
  • 395