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
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
---
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With