clojure实战——binding vs let

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*输出
  )
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值