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])