首页 > 代码库 > 关于Clojure的Var,我大概了解了70%

关于Clojure的Var,我大概了解了70%

对于Colure提供的Var,Ref,Agent,Atom不是不好理解,关键是不知道用在哪里?

随着编写clojure代码数量的增加,对于var有了更深的了解。下面就分享一下,如果能帮助你更快地理解var,将会是令人开心的事情。

java的变量是真的变量,你可以随时改变,按你的思维”自然“的改变,你甚至不会去注意它。但是clolure是不变为主,它的var需要好好思考一番。

1、产生var

    (def x 5)
    (println x) -------------> 5
    ;就产生了一个var,这里需要注意的是(println x)中的x在环境中解析成它的值,也就是5,但是var本身也是一个对象,如果你要得到var本身,就要用: #‘x,但这有什么用呢?有些special form或者macro需要用到。比如:
    (var-set x 6),你第一次使用var-set,有很大的可能性会这样写,但实际上是错误的,正确的是:
    (var-set #‘x 6),这样写就对了,但是和上面的代码结合起来还是错的,错误提示会是:java.lang.IllegalStateException: Can‘t change/establish root binding of: x with set。

2、改变var的值

(def ^:dynamic x 5)

(binding [x 6]
    (var-set (var x) 8) ; (var x) 和 #‘x 等价
    x) ---------> 8
;修改var的条件,必须是声明为dynamic,而且必须在binding里面。

3、改变var这么麻烦,而且2的示例代码有毫无意义,到底有什么用呢?

(def ^:dynamic *shiro-respnse*)

(deftest cssesion-request
  (binding [*shiro-respnse* ((csession-app) (sample-request))]
    (is (get-in *shiro-respnse* [:cookies :JSESSIONID]))))

Clojure的macro: with-precision,就是一个例子。但是理解var最好的办法是:你觉得自己的某一段代码必须使用var,否则代码会非常不舒服。

到那个时候,你对于var的理解才会有本质的进步。

由于clojure的测试只有一个:is。用来测试跟在后面的form的结果是真。上面的代码3,为了测试csession-app的返回的response是否包含JSESSIONID,如果不用binding,那么is后面的form会非常长,而且必须用do来串联多个is。用binding之后代码就清爽多了。

但是这里用binding其实根本没有体现binding的作用,完全可以用let来替代。

我之所以用binding,是因为我总算找到一处可以在自己的代码中使用binding的机会,并且it works。

4、用可变的var写clojure代码。(这其实是反clojure模式的)

(defn factorial [x]
         (with-local-vars [acc 1, cnt x]
           (while (> @cnt 0)
             (var-set acc (* @acc @cnt))
             (var-set cnt (dec @cnt)))
           @acc))
           
;这段代码对于java程序员就很好理解了。

5、真正使用var的场景,来自clojure的源代码

(defmacro with-precision
  "Sets the precision and rounding mode to be used for BigDecimal operations.

  Usage: (with-precision 10 (/ 1M 3))
  or:    (with-precision 10 :rounding HALF_DOWN (/ 1M 3))

  The rounding mode is one of CEILING, FLOOR, HALF_UP, HALF_DOWN,
  HALF_EVEN, UP, DOWN and UNNECESSARY; it defaults to HALF_UP."
  {:added "1.0"}
  [precision & exprs]
    (let [[body rm] (if (= (first exprs) :rounding)
                      [(next (next exprs))
                       `((. java.math.RoundingMode ~(second exprs)))]
                      [exprs nil])]
      `(binding [*math-context* (java.math.MathContext. ~precision ~@rm)]
         ~@body)))

这是一个macro,通过绑定环境,让代码得以正确的执行。那为什么这里不用let呢?你看代码的最后一行,你可以在你传入的body里面使用*match-context*,而且这个*match-context*是thread local的var,你在这块代码里面使用,不会干扰另外一块代码的*match-context*值。

关于Clojure的Var,我大概了解了70%