Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scoping rules in Clojure

Tags:

clojure

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
like image 267
unj2 Avatar asked Nov 21 '09 03:11

unj2


1 Answers

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
---
like image 154
Brian Carper Avatar answered Jan 15 '23 17:01

Brian Carper