do和塊語句
在Scala中,花括號{}括起來的語句構成一個block,它的值就是最後一個語句的值。
scala> val a = { | println("a") | 1} a a: Int = 1
{println("a"); 1}的值為1。
在Clojure中,有時需要使多個form組成一個block, 這個block的值是最後一個form的值。這時候就用do
user=> (def a (do (println "a") 1)) a #'user/a user=> a 1
do takes any number of forms, evaluates them all, and returns the last.
do接受任意多的form作為引數,對它們分別求值,然後返回最後一個form的值。
迴圈
有哪些要素才能構成一個迴圈?
在Java中
- 首先,我們需要提供一個在每次迴圈中都會被執行的語句——迴圈體
- 如果不是無限迴圈,我們需要提供退出條件,當這個條件滿足時,不再迴圈。
在Clojure中
- 首先,我們需要提供一個在每次迴圈中都會被執行的語句——迴圈體
- 我們需要提供迴圈條件。它這個條件滿足時,繼續下一次迴圈。
也就是說Java需要我們告訴它什麼時候退出迴圈,而Clojure需要我們告訴它何時繼續迴圈。
Java的for迴圈是這樣的
for(int i = 0; i < 10; i++) System.out.println(i);
可以認為 i< 10, i++以及System.out.println(i)構成了迴圈體。退出條件為i<10。
while迴圈與for迴圈的不同之處在於while無法宣告只在迴圈內部使用的變數。在上邊的for迴圈中, i只在迴圈內部使用。如果我們想讓while有類似的功能(當然,while沒這功能),那麼while需要接受一個初始化語法,變成
while(int i = 0)(i < 10){ println(i); i++; }
在Clojure中,同樣可以以binding的形式提供初始化語句, 以及提供迴圈體。這通過loop這種form來實現
(loop [bindings *] exprs*)
這就類似前邊這個加強版的while。同時,在while迴圈中需要break來打破迴圈; 在Clojure中,我們需要一種form來繼續迴圈,這就是recur。可以認為Java的迴圈是主動的,而Clojure中的是被動的,你必須在程式碼中驅動它前進。
(loop [a 0] (if (< a 10) (do (println a) (recur (+ 1 a)))))
recur使得程式重新開始執行loop。但是如何程式中是簡單地重新執行loop,它就只是原地踏步,因為所有的繫結都始終是初始值。所以recur不僅轉變了程式的執行流,而且修改了loop開始的繫結。即,recur使得loop開始對a的繫結變成了(+ 1 a)。
假如,我們在loop開始的時候多提供一個繫結
(loop [a 0 b 1] (if (< a 10) (do (println a) (recur (+ 1 a)))))
REPL就會告訴我們提供給recur的引數個數不對
CompilerException java.lang.IllegalArgumentException: Mismatched argument count to recur, expected: 2 args, got: 1
實際上,recur不僅可以用於loop,也可以用於函式,它使得函式被重新執行。
舉個書上的例子
(defn countdown [result x] (if (zero? x) result (recur (conj result x) (dec x))))
執行(count down [] 5)會輸出返回值[5 4 3 2 1]
這種程式碼怎麼看著這麼眼熟呢?這不就像是尾遞迴嗎?