mapのループをforで表現する
前回(mapと再帰で木構造を扱う - Programmer's Note)は、mapと再帰で木構造を扱う処理を書いたが、mapはシーケンスを順になめていく高階関数であって、いわゆるコレクションを扱うfor文と変わらない。 Clojureにもforマクロは用意されていて、かなり表現力が高い。
前回のscale-tree
関数は、
(defn scale-tree [tree factor] (map (fn [sub-tree] (if (coll? sub-tree) (scale-tree sub-tree factor) (* sub-tree factor))) tree))
だが、これをforを使って書き直すと以下になる。
(defn scale-tree [tree factor] (for [item tree] (if (coll? item) (scale-tree item factor) (* item factor))))
処理内容がだいぶ読みやすくなった。map
は汎用性が高く利点はあるが、無名関数の定義(fn [..] ..)
が入る分コードが煩雑になり読みにくくなる。
Schemeなどだと無名関数はいわゆるlambda関数なので、表記が(lambda (...) ..)
でさらに読みにくい。
for
の場合は、扱うコレクションと各要素を代入する変数が一緒に、要素に対する処理の手前に置かれるので、コードを追いやすい。
プログラムの可読性はシンタックスがもろに効いてくることを実感させられる。
さて、SICPにはmap
を使った以下のような例(練習問題)が出てくる。
(defn unique-pairs [n] (reduce concat (map (fn [i] (map (fn [j] (list i j)) (range 1 i))) (range 1 (inc n)))))
これは、1 <= j < i <= n
のiとjのペアの集合を作る関数だ。
実行結果は以下のようになる。
(unique-pairs 6) ;-> ((2 1) (3 1) (3 2) (4 1) (4 2) (4 3) (5 1) (5 2) (5 3) (5 4) (6 1) (6 2) (6 3) (6 4) (6 5))
このコードはmap
を二重に使っていて読みにくいが、for
を使うとすっきりと表現できる。
(defn unique-pairs [n] (reduce concat (for [i (range 1 (inc n))] (for [j (range 1 i)] (list i j)))))
これにとどまらず、Clojureのfor
マクロはかなり便利で、二重ループはもっとすっきり表現できる。
(defn unique-pairs [n] (for [i (range 1 (inc n)) j (range 1 i)] (list i j)))
なんと、これだけで済んでしまった。 Clojure見事なり。