CLOVER🍀

That was when it all began.

Clojureの->マクロと->>マクロ

今回もきっかけは「実践プログラミングDSL」からだったりしますが、ClojureDocsとかでもたまに見かけて気になっていた表現があります。

Clojureのこれ

->

とこれ

->>

です。

何だろ?と思っていたのですが、書籍にも出てきたので調べてみることにしました。

->

  • >はマクロで、->のすぐ次に書いた値を、続く関数呼び出しの最初の引数として適用できます。関数呼び出しが続く場合は、直前の関数呼び出しの結果が次の関数呼び出しの最初の引数となります。

…書いた方がわかりやすいかも。

(println (-> "test" .toUpperCase))  ;; => TEST
(println (-> "t e s t" .toUpperCase (.split " ") seq)) ;; => (T E S T)

最初の例は、文字列「test」にString#toUpperCaseをかけています。次の例は、String#toUpperCaseの結果をString#splitしています。

普通に書くと、こうですよね。

(println (.toUpperCase "test"))
(println (seq (.split (.toUpperCase "t e s t") " ")))

その他、サンプルコード。

(use '[clojure.string :only (split)])
(def value
  (-> "a b c d" .toUpperCase (.replace \A \Z) (split #" ") first))

(println value) ;; => Z

文字列「a b c d」を大文字に変えて、「A」を「Z」に置換、空白で分割してシーケンスの最初の要素を取る、です。
意味的に同じものは、こちら。

(use '[clojure.string :only (split)])
(def value
  (first (split (.replace (.toUpperCase "a b c d") \A \Z) #" ")))

(println value)  ;; => Z
  • >マクロを使った方が、前から読んでいけるので随分と見やすいと思います。

途中の結果がnilを返すと、そこで止まるようですね。

(def person {:name "Kazuhira"
             :organization {:name "Little Wings"
                            :since {:year "2005"
                                    :month "05"
                                    :day "03"}}})

(println (-> person :name)) ;; => Kazuhira
(println (-> person :organization :since :year))  ;; => 2005
(println (-> person :organization :since :foo))  ;; => nil

->>

こちらもマクロです。->との違いは、->が次の関数呼び出しの最初の引数になるのに対して、->>は最後の引数になるということです。

まずはサンプル。

(println
 (->> (range)
      (map #(* % 10))
      (take 10)
      (reduce +)))  ;; => 450

rangeによるシーケンスを生成して、各要素を10倍、うち先頭10個を引っ張ってきて最後に合算します。
普通に書くと、こんな感じ。

(println
 (reduce + (map #(* % 10) (take 10 (range)))))  ;; => 450

もいっちょ、サンプル。

(def value
  (->> "a b c d"
       .toUpperCase
       (filter #(not (Character/isWhitespace %)))
       (map #(str "***" % "***"))))

(println value)  ;; (***A*** ***B*** ***C*** ***D***)

文字列「a b c d」を大文字に変換してシーケンスに変えて空白以外にフィルタ、最後に「***」を前後に付けて装飾。
普通に書いた例は、こちら。

(def value
  (map #(str "***" % "***")
       (filter #(not (Character/isWhitespace %))
               (.toUpperCase "a b c d"))))

(println value) ;; (***A*** ***B*** ***C*** ***D***)

やっぱり、前から読める分だけ見やすいですね。

  • >マクロは最初の引数に、->>マクロは最後の引数と適用位置が限定されるので、関数の引数の位置によっては使うのが難しかったりするのですが、キレイにハマった場合はすごく見やすいコードになります。

こういう書き方ができるのを見たりすると、Clojureを学んでいて新鮮なものが得られる感じがして楽しいです♪