tags:

views:

231

answers:

2

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
+8  A: 

Clojure uses both lexical scope let for symbols and dynamic scope binding for vars check out clojure's vars documentation.

  • "entering function": doing fine so far! the symbol x resolves to the var and this is grabbing the "root binding" of the var x.
  • "after let": a local binding covered up the var, the symbol x is now 100 not a var.
  • "after let and dummy2": the x in dummy-fn2 refers to the var x, so it uses the root binding of x and returned one more than that (+ 1 1)
  • "after binding": tricky one! binding dynamically replaced the root binding of the var named x (which was 1) with 100, but the local symbol x is not the var anymore so you get the local binding.
  • "after binding and dummy2": the binding replaced the root value of the var x with 100 and this returned one more than that (+ 100 1)
Arthur Ulfeldt
it looks like binding replaces "the var with name x", not "the var that the symbol x resolves to" is this correct?
Arthur Ulfeldt
What does "the var with name x" mean?
Brian Carper
Whichever var is returned by (resolve 'x).
Mike Douglas
I mean, I think "the var that symbol x resolves to" and "the var with name x" are essentially the same thing; I don't see any distinction.
Brian Carper
Oops, now I see what you're referring too. Carper's right, there isn't a distinction. `let` shadows the Var with a local, `binding` shadows the value of Var `x` with a new value. There is only ever one Var.
Mike Douglas
"the var that symbol x resolves to" and "the var with name x" differ when x currently resolves to something that is not a var. like the number 2
Arthur Ulfeldt
+7  A: 

let hides 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
---
Brian Carper