clojure实战——binding vs let
binding vs let(1)
- binding 创建了一个动态的域绑定;用于动态绑定动态的Var,即必须先创建一个动态绑定的Var,然后才可以进行动态绑定。
binding 用于线程内绑定。使用binding创建的绑定不能被其他线程可见。
clojure中每个Var都可以有一个”根值”(也可以不设置),它是对所有线程可见的。而一个可动态绑定的Var不能直接再绑定它的根植,
;; 而线程内进行再次绑定(使用binding)绑定的值只对本线程内的binding form内有效。let 只是为某些值创建了一个词法域的不可变别名,也就是说let本质上说算不上真正意义的”绑定”。let并不受外部是否已经定义了这个变量,如果外部有同名的,它会覆盖。
示例:
(comment
(binding [n 1] n)
;; => CompilerException java.lang.RuntimeException: Unable to resolve var: n in this context...
(def n 0)
(binding [n 1] n)
;; => CompilerException java.lang.IllegalStateException: Can't dynamically bind non-dynamic var: clojure-study.syntax/n,
;; 不能动态绑定非动态的var。
(def ^:dynamic n 0)
(binding [n 1] n)
;; => 1
(let [n 0] n)
;; => 0
;; n只是0的一个别名,它作用在let这个词法作用域内,
)
binding vs let(2)
- binding 可以使用限定名(带命名空间的名字);
- let 不可以。
示例:
(comment
(def ^:dynamic x 0)
(binding [clojure-study.let-binding/x 1]
clojure-study.let-binding/x)
;; => 1
(let [clojure-study.let-binding/x "name"]
clojure-study.let-binding/x)
;; => CompilerException clojure.lang.ExceptionInfo: Call to clojure.core/let did not conform to spec ...
)
binding vs let(3)
- let 是串行的赋值,因此后面的binding可以用前面binding的值,一个binding可以被重复绑定不同的值
- binding 则不可以,因为在binding中,所有的初始值都会在被绑定之前计算,即使前面有绑定语句。
示例:
(comment
(let [a 1 b 2 a b] a)
;; => 2
;; b可以利用前面的绑定a, a可以被重复绑定。
(def ^:dynamic x 2)
(def ^:dynamic y 3)
(binding [x 1 x 2] x)
;; => 2
;; => CompilerException java.lang.RuntimeException: EOF while reading
(binding [x 1 y (+ 2 x)]
[x 4])
;; => [1 4]
;; y并没有用前面x绑定的值1,而是x的初始值2
;; (binding [x 1 y (+ x 1)] y) 中,(+ x 1)这个求值过程会先于 [x 1]这个绑定过程,所以(+ x 1)中x还是2
)
binding vs let(4)
- let的绑定是不可变的。
- binding的绑定是本地线程可变的,可以用set!函数修改。
示例:
(comment
(let [a 1]
(set! a 2))
;; => CompilerException java.lang.IllegalArgumentException: Cannot assign to non-mutable: a
(def ^:dynamic y-3 3)
(set! y-3 6)
;; => CompilerException java.lang.IllegalStateException: Can't change/establish root binding of: y-3 with set
;; 不可以改变var的根值
(binding [y-3 4]
(set! y-3 5))
;; => 5
;; 这里改变的不是y-3的根值,而是它在binding这个域内的值,所以可以set!
)
binding vs let(5)
- let 绑定的作用范围是词法域,类似于给一个局部变量赋值。
- binding 绑定则是为一个全局变量绑定一个值,但这个新绑定的值只作用于binding form所包含的代码,以及这些代码的调用中。
- 不是所有使用到这个全局变量的代码都会看到这个新绑定的值,而是binding作用范围内的代码!!!
示例:
(comment
(let [n 2] n)
;; => 2
(prn n)
;; => CompilerException java.lang.RuntimeException: Unable to resolve symbol: n
(def ^:dynamic n 1)
(defn prn-fn [description]
(prn description n))
(binding [n 3]
(prn-fn "binding form 内看到的n == "))
;; => "binding form 内看到的n == " 3
(prn-fn "binding form范围之外看到的n ===")
;; => "binding form范围之外看到的n ===" 1
;; 即上面binding中绑定的n的新值并不会影响在其范围之外的n。
)
一个应用示例
A、B两个命名空间,都用println输出日志(默认都是输出到out).
现在想将两个输出日志分开,以免混淆,A继续输出到out,而B则输出到err现实场景:想将第三方库中标准输出的日志与应用自身业务日志分离开来。
(comment
(ns A )
(println "log in A")
(ns B)
(println "log in B")
(ns C)
(binding [*out* *err*]
(require '[clojure-study.A :as a]))
;; 这样A命名空间下的所有使用的标准输出*out*的输出,都会重定向到*err*输出
)