对于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*值。