原文請參考,如有問題和歧義請指正,謝謝:)
http://clojure.org/vars
變量和全局環(huán)境
Clojure是個很實(shí)用的語言,偶爾需要將維護(hù)和改變數(shù)據(jù)的值。她提供了4種不同的方式來操作變量:Vars, Refs, Agents, 和Atoms。Vars機(jī)制是是指向一個可改變的數(shù)據(jù)的位置,你可以為每個線程動態(tài)的綁定(制定一個新的存儲位置)一個新值。Vars可以初始化根綁定(不是必須的),綁定的值對于所有線程都是共享的,但卻別的線程就不能重新綁定。因此,要么Var可以為每個線程綁定值,要么使用根綁定。
下面的special form def 創(chuàng)建了一個Var,如果Var不存在和沒有給初始化,var就是不綁定的(不允許創(chuàng)建非動態(tài)的Var,必須顯式指定根綁定):
user=> (def x)
#'user/x
user=> x
java.lang.IllegalStateException: Var user/x is unbound.
為根值初始化(如果存在,就被再次綁定)
user=> (def x 1)
#'user/x
user=> x
1
默認(rèn)情況下(定義的時候初始化了根綁定),Vars是靜態(tài)的(static),但是,建立動態(tài)Var的定義可以通過元數(shù)據(jù)標(biāo)記的方式,然后在線程用時通過binding來指定。
user=> (def ^:dynamic x 1)
user=> (def ^:dynamic y 1)
user=> (+ x y)
2
user=> (binding [x 2 y 3]
(+ x y))
5
user=> (+ x y)
2
binding被創(chuàng)建后其他線程是是不可見的。創(chuàng)建的binding可以被賦值,也就是在沒有離開調(diào)用堆棧之前可以被上下文訪問??梢栽谝粔K代碼之前設(shè)置matadata標(biāo)簽:dynamic來指定:
user=> (def ^:dynamic x 1)
#'user/x
user=> (meta #'x)
{:ns #<Namespace user>, :name x, :dynamic true, :line 30, :file "NO_SOURCE_PATH"}
user=> (binding [x 2] (println x))
2
nil
user=> x
1
user=>
如果你想讓函數(shù)編譯為static的,并且指定返回值,可以看下面的例子(速度提升不少,關(guān)鍵的調(diào)用函數(shù)可以采用這種方式加速):
(defn fib [n] (if (<= n 1)
1
(+ (fib (dec n)) (fib (- n 2)))))
#'user/fib
(defn ^:static fib2 ^long [^long n]
(if (<= n 1)
1
(+ (fib2 (dec n)) (fib2 (- n 2)))))
#'user/fib2
user=> (time (fib 38))
"Elapsed time: 1831.113 msecs"
63245986
user=> (time (fib2 38))
"Elapsed time: 328.715 msecs"
63245986
user=> (meta (var fib))
{:arglists ([n]), :ns #<Namespace user>, :name fib, :line 1, :file "NO_SOURCE_PATH"}
user=> (meta (var fib2))
{:arglists ([n]), :ns #<Namespace user>, :name fib2, :static true, :line 4, :file "NO_SOURCE_PATH"}
user=>
在上下文中可能需要重定義靜態(tài)變量,從Clojure1.3開始提供with-redefs和with-redefs-fn這兩個宏來修改。
定義函數(shù)的defn也是Vars的存儲方式,也可以在運(yùn)行時被重定義。這也為aop編程帶來很多方便,例如:你可以封裝一個類似logging函數(shù)給調(diào)用的上下文或者或者線程。
(set! var-symbol expr)
將Vars指定為special form
當(dāng)?shù)匾粋€操作符為symbol的時候,它必須是全局變量。當(dāng)前線程綁定的值就是后面的expr,也就是說必須是Thread-local的才可以,否則將會拋出一個使用set!來設(shè)定根綁定變量的錯誤。變量的表達(dá)式expr必須有返回值。
注意,你不能賦值給一個函數(shù)的參數(shù)或者本地綁定,只能是java的字段Vars Refs和Agents,因?yàn)檫@些數(shù)據(jù)在Clojure里可不變的。
使用set為java字段設(shè)置值,可以查看 Java Interop.
Interning
命名空間維護(hù)了每個Var對象的全局符號映射。如果使用def定義變量沒有在當(dāng)前的命名空間找到該符號,就創(chuàng)建一個,否則使用現(xiàn)有的。創(chuàng)建或者尋找的過程被稱作interning。這就意味著,除非Var對象取消映射,否則Var對象每次被查詢,所以請?jiān)谘h(huán)中千萬不要引用Var的全局變量,否則將非常慢,通過let或者binding讓全局變量取消映射來提高速度。命名空間在Evaluation中構(gòu)建了全局環(huán)境,編譯器也把所有free symbols當(dāng)做Vars來解析了。
可以使用閱讀宏(Reader)#’來得到Var對象的內(nèi)部的值。
Non-interned的類型的變量
可以通過with-local-vars來創(chuàng)建non-interned類型的變量,在free symbol解析的時候?qū)⒉粫话l(fā)現(xiàn),這些值只能被手工的訪問,但是也可以用作當(dāng)前線程的變量。
user=> (defn factorial [x]
(with-local-vars [acc 1, cnt x]
(while (> @cnt 0)
(var-set acc (* @acc @cnt))
(var-set cnt (dec @cnt)))
@acc))
#'user/factorial
user=> (factorial 7)
5040