読者です 読者をやめる 読者になる 読者になる

あと味

たくさん情報を食べて、たくさん発信すると、あとになって味わい深い。

Clojureの->と->>の使い方

Clojureのコード読むと、->(ハイフン・大なり、ハイフン・不等号)とか->>(ハイフン・大なり・大なり、ハイフン・不等号・不等号)とかの記号を見かけるのですが、Googleで検索しようにも検索できないので、はじめ何やってるのかわからなくて大変でした。*1

同じ人もたくさんいるだろうということで、使い方を書いておきます。*2

->(ハイフン・大なり、ハイフン・不等号)の使い方

ClojureDocsの該当項目を見れば、使い方はなんとなくわかります。

動作を見ると、partialとcomposeの融合版みたいな感じですね。これは関数しか使えない言語(マクロのない言語)ではできない機能です。素晴らしい。

ClojureDocsの例を確認します。よくあるパターンですね。

"a b c d"という文字列を全て大文字にしたい。そんな時は、以下のように書けます。

その後、"A""X"に変えたいと思ったら、.replaceを挟むことになります。

さらにその後、先頭の文字だけ取り出したいと思ったら、.splitでリストにして、firstで先頭の要素だけ取り出すかもしれません。

こうなってくるとものすごく可視性が悪くなりますね。

Clojureでは、このように引数をたらい回しにする際に、->マクロが使えます。

最初の引数が、元になるデータ、それ以降の引数が関数名もしくは、引数の2つ目を省略した「関数を実行してるっぽい記述」*3を渡します。

より具体的に動きを把握するためにソースを見てみましょう。

引数の数によって処理を変えています。*4

引数が[x]の1つの場合、引数が[x form]の2つの場合、引数が[x form & more]の3つ以上の場合です。

引数がひとつの場合はxをそのまま返します。([x] x)の部分ですね。

引数が2つの場合は、第2引数がシーケンスであるかどうかで処理が変わります。

まず、単純なシーケンスでなかったパターン。先ほどの例で言うと、.toUpperCaseとか、firstとかを指定していた部分ですね。

シーケンスでない場合の本体部分を見ると、(list form x)とあります。

第2引数と第1引数からなるリストを返しているということですね。要するに、(.toUpperCase x)とか、(first x)のように変換しているということです。

では、シーケンスであるパターン。先ほどの例で言うと、(.split " ")とか、(.replace "A" "X")とかを指定していた部分ですね。

本体部分を見ると、(with-meta `(~(first form) ~x ~@(next form)) (meta form))とあります。

あらかじめ、第2引数のメタ情報を取り出して、変換後のリストに付加していますね。メタ情報を壊さないので、このマクロはメタ情報を取り出して、加工して...といった処理も可能になっているようです。

with-metaの第1引数に指定している箇所を見ると、シーケンスであるformの最初を取り出して、xを挟んで、formの残りをつなげています。要するに、(.replace "A" "X")を(.replace x "A" "X")に変換しているということです。

引数が3つ以上の場合は、moreが尽きるまで、再帰的に->を適用しています。結果的に、第3引数以降の全ての引数は、引数が2つのパターンに変換されるようですね。

->の使い方まとめ

第1引数に指定した値を、第2引数以降にしていした関数名や関数実行っぽいものの第1引数に当てはめていくマクロです。言葉で表現するのムズイですね...

変換後の関数の第1引数にしか束縛できないというのが、苦しい点もあるかもしれませんが、partialをうまく使えば、どんなパターンにも応用できそうです。

map関数は第1引数に関数、第2引数以降はシーケンスを指定します。->は変換後の関数の第1引数にしか束縛できないので、単純にmapを埋めても、意図した動作になりません。ということで、partialを使って以下のような感じにすれば、第1引数縛りでもうまく動きます。*5

->>(ハイフン・大なり・大なり、ハイフン・不等号・不等号)の使い方

ほとんど->マクロと同様なので、簡潔に。

使い方は以下のような感じです。

ソースはこうです。

第1引数に指定した値を変換後の関数の最後の引数に束縛しているという点が異なりますね。

あともう一点、->の方は引数にシーケンス以外、つまり関数名が入ることも想定されていますが、こちらの方はシーケンスを渡すことが前提なので、関数名にはカッコを付ける必要があります。

->>の使い方まとめ

高階関数のインターフェイスなどは、第1引数に関数、第1引数以降にシーケンスというパターンが多いので、->>の方を使うことになります。

まとめ

関数の引数の順番によって、使い分ければ良いと思います。個人的に、他の言語でもcompose的な関数をよく使うので、重宝しそうです。

マクロ、自分で書いたことはありませんが、力でねじ伏せる感じでカッコイイですね。

あと、ClojureDocsはその機能のソースが閲覧できるのが良いと思いました。

*1:なんとかリーダーマクロって読むんだと思うんですけど、ドキュメントに書いてないしなぁ。

*2:はてなダイアリーは、タイトルに文字参照が入っていると、文字参照のまま表示してしまうようなので、>は全角にしました。

*3:マクロなので、関数が展開される前に渡ります。この場合、なんて表現すればいいんだろう...ということで、「関数を実行してるっぽい記述」としました。

*4:パターンマッチングとはちょっと違います。defnの別形式らしい。

*5:この場合->>を使う方が良いですけどね