由於 Clojure 中的資料結構都是不可變 (Immutable),所以沒有主流程式語言的 for 迴圈,因爲 for 迴圈需要在每次迭代 (Iteration) 修改變數以達到迭代的功能。透過遞迴與函式也能夠做到迴圈的功能。
如果想要遍歷序列,在每次取得序列中的元素時,就執行一次程式,可以使用 doseq
。doseq
接受一個向量跟運算式本體,向量中以類似 let
方式命名一個符號,每次循環繫結序列中的元素:
(doseq [x [1 2 3]]
(println x))
;; => 1
;; => 2
;; => 3
;; => nil
每次迭代時,符號 x
繫結了元素的內容。
doseq
在遍歷元素時還可設定條件修飾子 (Modifier),決定何時對遍歷時的運算式求值、設定迭代的終止條件、以及在每次遍歷中繫結符號作爲使用:
(doseq [x (range 5)
y [10 20 30]
:let [z (* x y)]
:when (odd? x)]
(println x y z))
;; => 1 10 10
;; => 1 20 20
;; => 1 30 30
;; => 3 10 30
;; => 3 20 60
;; => 3 30 90
;; => nil
以上範例中,x
爲 0 到 4,y
爲 10 20 30,每次迭代還會設定符號 z
爲 x
與 y
相乘的值。迭代時當 x
爲奇數才會運行 (println x y z)
。
以下範例則示範使用 :while
關鍵字,設定迭代停止條件。當 y
大於或等於 30 時,迭代便終止 :
(doseq [x (range 99)
:let [y (* x x)]
:while (< y 30)]
(println [x y]))
;; => [0 0]
;; => [1 1]
;; => [2 4]
;; => [3 9]
;; => [4 16]
;; => [5 25]
;; => nil
dotimes
與 doseq
類似,第一個參數的向量中包含符號與數字,數字代表迭代的次數,符號則繫結了每次迭代的次數。迭代的次數爲 0 到 (n - 1)。
(dotimes [x 3]
(println x))
;; => 0
;; => 1
;; => 2
;; => nil
while
非常類似於在其他命令式 (Imperative) 程式語言的兄弟,例如 Ruby 或 Java。while
接受一個測試運算式與本體運算式,當此運算式求值結果爲假時,才會終止對本體運算求值的循環。
(def x (atom 5))
(while (> @x 0)
(do
(println @x)
(swap! x dec)))
;; => 5
;; => 4
;; => 3
;; => 2
;; => 1
nil
以上範例中的 x
爲 Clojure 引用型態 (Reference type) 中的原子 (Atom) 型態,使用 atom
與參數建立原子型態,內容爲 5。若是想要取用原子型態中的資料,須使用 @
或 deref
才可取用到內容。引用型態將會在後續文章詳細解說。
範例的內容爲每次迭代都會檢查 x
是否大於 0,若否則印出其值並將 x
指向的值遞減。
以上提到的控制迭代循環的 doseq
、dotimes
以及 while
都是利用 Clojure 迭代的基本元素 loop
與 recur
來達成。loop
特殊形式 (Special form) 利用類似 let
語法來繫結循環時會用到的符號與資料的對應,之後包含了循環時運行的本體運算式,recur
運算式則設立下次循環時使用到的新值。以下範例使用 loop
與 recur
示範倒數的功能:
(loop [n 5] ; 1
(if (zero? n)
n ; 2
(do
(println n)
(recur (dec n))))) ; 3
;; => 5
;; => 4
;; => 3
;; => 2
;; => 1
;; => 0
loop
在此建立了 n
的資料繫結,內容初始爲 5。
此處設立了當到達終止條件 n 爲零時,將執行的運算式。loop
的返回值即爲此運算式求值後的結果。
最後在此處 recur
設立了下次循環時,loop
已建立的符號 n
的新值,在這裡爲 n - 1。設定完成後,n 將會以新的值從 1
處重新開始循環。
由於 JVM 缺乏尾遞迴 (Tail Recursion) 的最佳化功能,以及遞迴時會消耗記憶體的問題,Clojure 中的使用慣例是利用 loop
與 recur
達成迭代的功能。
recur
除了可以返回 loop
設立的遞迴點 (Recursion point) 之外,也可以返回由函式定義產生的遞迴點。以下範例使用 defn
與 recur
來實作倒數功能.
(defn countdown [x]
(if (zero? x)
x
(do
(println x)
(recur (dec x)))))
;; => #'user/countdown
(countdown 3)
;; => 3
;; => 2
;; => 1
;; => 0
(未完待續)