なんとなく Clojure で書かれたコードを読みながら簡単だけど意外に知らないかもなーと思った便利そうなものを紹介してみようと思う(分かんない、たぶん皆知ってる)。
課題: 1 から 100 までの整数の列から偶数と奇数をそれぞれ集めてください
こういう課題があったときにどう書きますか?という話で、 Clojure に慣れている人ならすぐに回答出来ると思うけど慣れてない人はこんなコードを書く気がします。
(def numbers (range 1 101)) ;; (1 2 3 4 ... 97 98 99 100)
(let [even-numbers (filter even? numbers)
odd-numbers (filter odd? numbers)]
[even-numbers odd-numbers]) ;; [(2 4 6 8 ... 94 96 98 100) (1 3 5 7 ... 93 95 97 99)]
これの何が悪いのかと言われると何も悪くないと思う(強いて言えば冗長?)けど、ただ今回はもうちょっと Clojure のらしさを取り入れたコードを紹介したいので書き直しを試みてみます。
まず、 filter
関数が 2 回使われていますが、ここで (filter odd? x)
は (remove even? x)
であることに気が付きます。
これを書き換えてみます。
(let [even-numbers (filter even? numbers)
odd-numbers (remove even? numbers)]
[even-numbers odd-numbers]) ;; [(2 4 6 8 ... 94 96 98 100) (1 3 5 7 ... 93 95 97 99)]
すると偶然にも filter
と remove
関数の引数が全く同一になってしまいました。こうなるとどうにかしてまとめることが出来るような気がしてきますよね。 Clojure なら簡単にできます。
(let [[even-numbers odd-numbers :as result] ((juxt filter remove) even? numbers)]
result) ;; [(2 4 6 8 ... 94 96 98 100) (1 3 5 7 ... 93 95 97 99)]
出来ました。結果は同じですね。
ここで注目して欲しいのは juxt
関数でこいつは何をしてくれる関数かというと doc を引けば分かります。
clojure.core/juxt
([f] [f g] [f g h] [f g h & fs])
Takes a set of functions and returns a fn that is the juxtaposition
of those fns. The returned fn takes a variable number of args, and
returns a vector containing the result of applying each fn to the
args (left-to-right).
((juxt a b c) x) => [(a x) (b x) (c x)]
簡単に言うと幾つかの関数を受け取って新しく関数を返し、その関数は受け取った引数をそれぞれの関数に適用した結果をベクターにして返しますよってことです。
今回の例で言えば ((juxt filter remove) even? numbers)
を [(filter even? numbers) (remove even? numbers)]
の省略形と捉えることができます。
結果がベクターなのでそれぞれの結果を使いたい場合は上でやってるように分配束縛してしまえばいいです。
今回の例は単純な数列でしたが、特定のベクターから条件にあったものとあってないものをそれぞれ取り出したいという需要は実際のプログラムでも度々あると思うので覚えておくといい気がします。
juxt
の別の便利な使い方
Clojure においてキーワードは関数と同様に扱えます。 (:name {:name "ayato_p" :age 24})
のように使いますよね。
{:name "ayato_p" :age 24}
みたいなマップから :name
と :age
を両方取り出したいというときどうしますか?
勿論、分配束縛も使えますが分配束縛を使いたいシーンは :name
と :age
の値をそれぞれ独立して使いたいシーンです。そうじゃないとき、純粋に値のベクターを得たいというシーンがあったとしたら、次のように書けます。
((juxt :name :age) {:name "ayato_p" :age 24 :weight :secret :height :secret}) ;; ["ayato_p" 24]
まとめ
juxt
便利なので使いましょう(乱用注意)。