本文由larrylgq編寫,轉載請註明出處:http://blog.csdn.net/larrylgq/article/details/7395261
作者:呂桂強
郵箱:larry.lv.word@gmail.com
clojure中變數可以分為詞法變數(lexical)和動態變數(dynamic),有點類似於其它語言中的局部變數和全域變數。
一個變數是一個不需要聲明檔案類型的可以儲存值的具名位置。它可以儲存任何類型的值,並且這些值帶有用於運行期檢查的類型資訊。並且如果類型出錯會被動態檢測到
eg:(將一個非數字對象傳給了+函數)
從這方面來看lisp是一個強型別的且動態類型的語言。
lisp是傳值的,但是傳遞的值是對象的引用。這點和java,python類似。意味著如果一個變數修改了它指向的一個可變對象,這個改動會被應用到任何引用該對象的變數。
一種引入新變數的方式是通過函數形參:形參列表定義了在函數被調用的時候儲存實參的變數。
eg:(defn foo [x y z] (+ x y z))
每次函數調用的時候clojure會建立新的綁定儲存有調用者傳遞來的實參,該綁定的生命週期一直到運行期結束。(遞迴函式的形參會在每一次函數調用的時候重新綁定)
另一種引入新變數的方式是使用let特殊操作符。
eg:(let [variable*] body-form*)
variable可以賦初值也可以不賦,下面是一個let將x,y,z分別綁定到1,2,nil上:
(let [[x 1] [y 2] z]
...)
let函數是一個調用匿名函數的宏,上面的例子可以展開為((fn [x y z] (...)) 1 2 nil)
let在調用結束後,如果該變數在let之前有引用,則會重新指向所引用的對象。
函數定義和let這兩種形式我們稱之為綁定形式,多個嵌套對同名變數的綁定,內層的變數綁定會覆蓋外層的。
clojure的閉包
如果一個匿名函數引用一個封閉範圍的變數就像這樣:
(let [count 0] #(fn [] (inc count)))
根據範圍規則lambda運算式中對count的引用是合法的,而且這個引用了count的匿名函數會被當作傳回值被let函數返回。
如果我們將這個運算式所建立的閉包賦值給一個全域變數例如:
(def *fn* (let [count 0] #(fn [] (inc count))))
這樣我們就可以在外部調用它
USER>(*fn*)
1
USER>(*fn*)
2
當然除此之外多個閉包也可以引用相同的變數
例如:
(let [count 0]
(list
#(fn [] (inc count))
#(fn [] (deccount))
#(fn [] count)
)
)
動態變數
Clojure裡面有4種動態變數類型:Vars,Refs,Atoms 和Agents.
下面這個表格是它們之間的比較:
這個表格裡面提到的函數我們會在後面介紹。
|
Var |
Ref |
Atom |
Agent |
目的 |
同步對一個本地線程的變數的修改 |
同步對於一個或者多個動態變數修改 |
同步對於一個變數進行修改 |
對一個變數進行非同步修改 |
建立方法 |
(def name initial-value) |
(ref initial-value) |
(atom initial-value) |
(agent initial-value) |
修改方法 |
(def name new-value) 給變數賦一個新值(alter-var-root (var name) update-fn args) 使用var和fn動態修改一個函數的引用
(set! name new-value) 用在binding內部,修改一個執行緒區域的值
|
(ref-set ref new-value)
給變數賦予一個新值,必須在dosync裡面調用 (alter ref update-fn arguments)
修改ref的值,必須在dosync裡面調用,如果事務開始之後值改變了,那麼當前事務會進行重試
(commute ref update-fn arguments) 修改ref的值,必須在dosync 裡面調用,即使事務開始之後值改變了,當前事務不會進行重試
|
(reset! atom new-value) 不管舊的值是什麼樣子,都會儲存新值 (compare-and-set! atom current-value new-value)設定新值之前檢查舊值如果和current-value相同才會設定並返回true,否則只返回false
(swap! atom update-fn arguments)對compare-and-set!的封裝,當返回false的時候會自動重試
|
(send agent update-fn arguments) 使用的線程池是(java.util.concurrent.Executors.newFixedThreadPool)線程個數是cpu個數+2 (send-off agent update-fn arguments)使用的線程池是(java.util.concurrent.Executors.newCachedThreadPool) 線程的個數按照實際需要分配
|
註:
1:Software Transactional Memory (STM):
STM事務裡面做的修改只能在事務提交之後才能被別的線程看到。這實現了ACID裡面的A(原子性)和I(隔離性)
事務開始之後,如果有別的線程對這個Ref做了改動,事務將會復原到開始的狀態,這就實現了C(一致性)
2:send 在分配一個action給Agent之後會立刻返回,action運行結束後,將傳回值賦值給Agent。
使用的線程池是(java.util.concurrent.Executors.newFixedThreadPool)線程個數是cpu個數+2
send-off與set類似,使用的線程池是(java.util.concurrent.Executors.newCachedThreadPool)
線程的個數按照實際需要分配。
當send,send-off 函數在事務裡調用的時候。該action會一直等到線程提交的時候才被發送給另外一個線程去執行。