2012-05-08 4Clojureを楽しむ(5)〜#()マクロ〜
4Clojureで遊びながら学ぶ。今日は4Clojureから気づいたことを書く。今日は#()
リーダーマクロについて書く。
fn, #() はなくても良い
"Write a function"などと書かれていてもfn
や#()
で書きはじめる必要はない。これは4Clojureのためのテクニックである。
例えば、ある数値のシーケンスから奇数だけを取り出すような問題があったとする。
(= (__ [1 2 3 4 5]) [1 3 5])
(= (__ [2 4]) [])
(= (__ [0 1 2]) [1])
(fn [s] (filter odd? s))
や#(filter odd? %)
と解答したくなるかもしれないが、4Clojureではfilter odd?
で十分だ。(partial filter odd?)
である必要さえない。関数ではなくても良い。入力したものがそのまま設定される。
さほど重要ではないが、4Clojureに取り組むなら知っておいて損はない。
#()は最後に置換する
#(...)
は便利なリーダーマクロである。(fn [] ...)
よりも短く書けるし、引数もよしなに処理してくれる。しかし大きな弱点がある。それは入れ子にできないことだ。
例えば、ある数値のシーケンス5よりも大きい数だけを取り出すような問題があったとする。
(= (__ (range 10)) [6 7 8 9])
(= (__ (range 5)) [])
(= (__ (range 10 15)) [10 11 12 13 14])
#(filter #(> % 5) %)
と書きたくなるが、これはClojureでは動作しない。#()
は入れ子にできないからだ。この場合には(fn [s] (filter #(> % 5) s))
とする必要がある。(もちろん(partial filter #(> % 5))
でも良い。こちらの方が分かりやすいかもしれない。)
これくらいの問題なら見た瞬間に入れ子になりそうだと気づく。しかし複雑な問題だったり、Clojureに慣れていないと複雑な条件を組みたてているうちに#()
を入れ子にしてしまうことがままある。
対策としては最初はすべてfn
で書いてしまうことだ。最後に提出する前に、あるいはテストをクリアした後に内側のものだけを#()
にすれば良い。
その他の注意点
おまけとしてその他の#()
の注意点をいくつか書く。まず#()
だと分配束縛ができない。ほかにも[x]
を返すために[x]
と書くことができない。具体例で説明しよう。
フィボナッチ数列の先頭n個を返す問題がある。
(= (__ 1) [1])
(= (__ 3) [1 1 2])
(= (__ 5) [1 1 2 3 5])
はじめぼくは次のように書いていた。
(fn [n] (take n (map second
(iterate #(vector (second %) (+ (first %) (second %))) [0 1]))))
この解はそんなに悪くない。Clojureらしい解き方だ。[[0 1] [1 1] [1 2] [2 3] [3 5] ...]
という遅延シーケンスを生成し、各要素を2番目だけに変換し、先頭n個を取り出す。悪くない。
でももっとうまく書ける。うるさいfirst/secondをなくすことができる。
#(take % (map second (iterate (fn [[x y]] [y (+ x y)]) [0 1])))
考え方は同じだ。違うのは#()
をfn
に置き換えて分配束縛を使うようにしたことだ。(fn [[x y]] ...)
とすることでうるさいfirst/secondはなくなった。コアな部分がより分かりやすく読める。また#()
だと([...])
の形を避けるために#(vector ...)
としていたがfn
なら[]
で書ける。
外側に#()
があることは先程の内側のfn
を#()
に書き換えるというルールには反する。しかし、そもそもフィボナッチ数列としては(map second ...)
の時点でできあがっている。この問題にとりあえず合わせるためのtake n
なので、「一時的な代用として使っている」という観点で#()
を見れば、より自然な位置で使用されているように思える。
便利な記法である#()
だが、注意点もある。うまく使っていきたい。
60 min.