TL;DR
- コメントは
;、commentマクロ、次のフォームを無視させる#_の 3 つの書き方がある。 ifは分岐ごとに 1 つの式しか書けず、複数のフォームを実行するときは副作用を明示するdoを使う。loopとrecurで再帰点を設定でき、recurの呼び出しは最適化される。
内容
コメント
Clojure でコメントを書く方法はいくつかある。
最もよく使われるのは ; で、行末までをコメントにする。慣習的に、;; と 2 つ並べて使われることが多い。
;; this is a commentClojure には、本文を無視して nil を返す comment マクロもある。既存のコードのブロックを囲むのに便利である。ただし、Clojure のリーダが読み込むので、正しい構文である必要がある。
(comment
(defn ignore-me []
;; not done yet
))リーダに次のフォームを読むが無視するように指示する #_ マクロもある。
(defn triple [number]
#_(println "debug triple number")
(* 3 number))
(triple 3) ; 9if による分岐
if は最初の引数を評価し、それが真であれば 2 つ目の引数を評価した結果を返す。
(defn is-small? [number]
(if (< number 100) "yes"))
(is-small? 50) ; "yes"この場合、偽であれば nil を返す。
(is-small? 50000) ; nil偽だった場合の結果も定義したいなら、式を第 3 引数として与える。
(defn is-small? [number]
(if (< number 100) "yes" "no"))
(is-small? 50000) ; "no"do による副作用の導入
Clojure の if は、それぞれの分岐先に 1 つの式しか書けない。このとき do を使うと、複数のフォームをすべて評価して最後の結果を返せる。
(defn is-small? [number]
(if (< number 100)
"yes"
(do
(println "Saw a bing number" number)
"no")))
(is-small? 50000) ; "no"
;; Saw a bing number 50000これは副作用の例である。多くの言語では、純粋な関数と副作用をごちゃ混ぜに使う。しかし、Clojure では副作用は明示しなければならず、それほど多く使うものでもない。do は「これから副作用がある」ことを明示する方法の 1 つである。
loop と recur による再帰
Clojure の loop は、万能ナイフのように扱える制御構文である。
(loop [bindings *] exprs+)loop は let のように動作し、束縛をした後に exprs を実行する。違いはloop が recur を使って再帰点を設定する点である。Clojure は末尾呼び出し最適化(TCO)を行わないが、この recur の呼び出しは最適化される。
(loop [result [] x 5]
(if (zero? x)
result
(recur (conj result x) (dec x))))
; [5 4 3 2 1]最初の処理では、loop は result に [] を、x に 5 を束縛する。x は 0 ではないので、recur は result と x を次の値で再束縛する。
result: 直前のxとresultを結合した値x: 直前のxをデクリメントした値
その後、処理は loop のブロックの先頭に戻る。これを x が 0 になるまで繰り返し、最終的に result を返す。
loop を使う代わりに、関数の先頭に再帰的に戻ることもできる。これを使うと、再帰関数を実装できる。
(defn countdown [result x]
(if (zero? x)
result
(recur (conj result x) (dec x))))
(countdown [] 5) ; [5 4 3 2 1]再帰は強力だが、その多くは Clojure のシーケンスライブラリで提供されているので、あまり使わないかもしれない。たとえば、countdown は次のように実現できる。
(into [] (take 5 (iterate dec 5)))
(into [] (drop-last (reverse (range 6))))
(vec (reverse (rest (range 6))))