Scoping rules in Clojure

clojure

Solution

`let` shadows the toplevel Var `x` with a local `x`. `let` does not create a Var or affect the toplevel Var; it binds some symbol such that local references to that symbol will be replaced with the `let`-bound value. `let` has lexical scope, so its bindings are only visible within the `let` form itself (not in functions called from within the `let`).

`binding` temporarily (thread-locally) changes the value of the toplevel Var `x`, that's all it does. If a `let` binding is in place, `binding` doesn't see it when deciding which value to change (and `let`'s bindings are not vars and are not alterable, so that' a good thing or it'd give you an error). And `binding` won't mask `let`. `binding` has dynamic scope, so its affects on toplevel Vars are visible within the `binding` form and in any function that is called from within the `binding` form.

Accessing the value of plain old `x` will give you whatever is at the top of the stack of bindings, either the most nested `let`-bound value of `x` (or the function paramater called `x`, or some value `x` is replaced with if you use your own macro, or other possibilities.), and only uses the current value of the toplevel Var `x` by default if there is no other binding in place.

Even if the toplevel Var `x` is masked by a `let`-bound `x`, you can always access the toplevel Var via `@#'x`. Try this version, maybe it'll make more sense:

(def x 1)

(defn dummy-fn2[]
  (println "x from dummy-fn2:" x)
  (+ x 1))  

(defn dummy-fn[]
  (println "entering function:" x)
  (println "var x:" @#'x)
  (dummy-fn2)
  (println "---")
  (let [x 100]
    (println "after let:" x)
    (println "var x:" @#'x)
    (dummy-fn2)
    (println "---")
    (let [x (dummy-fn2)]
      (println "after let and dummy-fn2:" x)
      (println "var x:" @#'x)
      (dummy-fn2)
      (println "---")
      (binding [x 888]
        (println "after binding:" x)
        (println "var x:" @#'x)
        (dummy-fn2)
        (println "---")
        (let [x (dummy-fn2)]
          (println "after binding and dummy2:" x)
          (println "var x:" @#'x)
          (dummy-fn2)
          (println "---"))))))

Gives:

entering function: 1
var x: 1
x from dummy-fn2: 1
---
after let: 100
var x: 1
x from dummy-fn2: 1
---
x from dummy-fn2: 1
after let and dummy-fn2: 2
var x: 1
x from dummy-fn2: 1
---
after binding: 2
var x: 888
x from dummy-fn2: 888
---
x from dummy-fn2: 888
after binding and dummy2: 889
var x: 888
x from dummy-fn2: 888
---

Problem

Even though I have used Clojure, I hadn't looked at the scoping rules in detail. I am getting more confused as I read the documentations. I made a small test to try out the scoping resolutions and am apalled at the complexity. Could somebody explain the intent and various rules that Clojure uses? ``` (def x 1) (defn dummy-fn2[] (+ x 1)) (defn dummy-fn[] (println "entering function: " x) (let [x 100] (println "after let: " x) (let [x (dummy-fn2)] (println "after let and dummy2: " x) (binding [x 100] (println "after binding: " x) (let [x (dummy-fn2)] (println "after binding and dummy2: " x)))))) 1:2 foo=> (dummy-fn) entering function: 1 after let: 100 after let and dummy2: 2 after binding: 2 after binding and dummy2: 101 nil ```

Original source