forマクロ
Clojureでプログラムを組んでいるうちに、forマクロが強力だということにやっと気づいたので、リファレンスを書いてみる。
forマクロとは
普通の(というか、CやJava系列の)言語では、forはループ文を表すが、Clojureではforマクロはループではなく、シーケンスを作るオペレータだ。
下記のコードでは、0〜9までの数をそれぞれ2倍した数のシーケンス(0, 2, 4, ..., 18)を返す。
(for [x (range 10)] (* x 2))
しかも、返されるシーケンスは遅延シーケンスなので、この式が評価されたときに中身が評価されるのではなく、シーケンスの中身が評価された時に評価される。
=> (let [x (for [x (range 3)] (do (println "x=" x) (* x 2)))] (prn "hoge") (prn x)) "hoge" (x= 0 x= 1 x= 2 0 2 4) nil
複数のシーケンス
複数シーケンスの指定は、多重ループに似た感じになる。
=> (for [x (range 3) y (range 3)] [x y]) ([0 0] [0 1] [0 2] [1 0] [1 1] [1 2] [2 0] [2 1] [2 2])
こんなのも可能。
=> (for [x [[1 2 3] [4 5 6] [7 8 9]] y x] y) (1 2 3 4 5 6 7 8 9)
:whenキーワード
上記のままだと、単なるmapの代わりだが、:whenキーワードを使うとfilter相当のこともできる。
=> (for [x (range 10) :when (even? x)] x) (0 2 4 6 8)
:whileキーワード
take-whileのようなこともできる。
=> (for [x (range 1000) :while (not= x 10)] x) (0 1 2 3 4 5 6 7 8 9)
:letキーワード
=> (for [x (range 10) :when (< (* x 2) 15)] (* x 2)) (0 2 4 6 8 10 12 14)
これでは、(* x 2)が2回計算しなければならず、効率が悪いので、、、
=> (for [x (range 10) :let [y (* x 2)] :when (< y 15)] y) (0 2 4 6 8 10 12 14)
:letを使えば、1回で済む。
複数のリストを同時に取り出す
forの場合、(for [x coll1 y coll2] ...)は2重ループ相当だが、mapのように複数のリストから同時に値を引き出したいときもある。
少し汚いかも知れないが、map listを使うのが簡単ではないかと思う。
=> (for [[x y] (map list [:a :b :c] [1 2 3])] [x y]) ([:a 1] [:b 2] [:c 3])