Clojure語法學習-迴圈

devos發表於2015-01-07

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中

  1. 首先,我們需要提供一個在每次迴圈中都會被執行的語句——迴圈體
  2. 如果不是無限迴圈,我們需要提供退出條件,當這個條件滿足時,不再迴圈。

在Clojure中

  1. 首先,我們需要提供一個在每次迴圈中都會被執行的語句——迴圈體
  2. 我們需要提供迴圈條件。它這個條件滿足時,繼續下一次迴圈。

也就是說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]

這種程式碼怎麼看著這麼眼熟呢?這不就像是尾遞迴嗎?

 

相關文章