万歳!cljsbuild!
マクロが使えるようになってまず一番最初にやることは何か?
もちろん、モナド内包表記の実現ですね!
HaskellのdoよりはScalaのforの方が慣れているのでこちらを採用しました。
動的型付けの言語でのモナドの実現はいくつか見てきましたが、明示的にコンテキストを指定するタイプはあまり好かないので、ClojureらしくProtocolで表現することにしました。
Monadic Protocol
通常モナドはunitとbindが実装され、モナド則
- bind f (unit x) == f x
- bind unit m == m
- あともう一個なんかあった気がする
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(defprotocol Monadic | |
(fmap [m f]) | |
(bind [m f])) |
とてもScala的。
モナド内包表記
Lispは神の言語と皆ネタにしていますが、Lispのマクロは本当に強力です。
既存の言語にある構文の全てはLispのマクロで表現できるのではと思えるくらいです。
Clojureの標準ライブラリではパターンマッチが出来ないので、準標準ライブラリであるcontribのcore.matchを使います。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(defmacro do-m [bindings expression] | |
(match [bindings] | |
[[name value]] | |
`(fmap ~value (fn [~name] ~expression)) | |
[[name value & bindings']] | |
`(bind ~value (fn [~name] (do-m ~bindings' ~expression))))) |
このdo-mマクロがどのように展開してくれるかというと
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(do-m | |
[a m | |
b (f a)] | |
(g a b)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(bind m (fn [a] (fmap (f a) (fn [b] (g a b))))) |
実際に動かしてみます。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
user=> (defn bind [m f] (mapcat f m)) | |
#'user/bind | |
user=> (defn fmap [m f] (map f m)) | |
#'user/fmap | |
user=> (do-m [a [1] b [(inc a)]] (+ a b)) | |
(3) |
素敵です。
Stateモナド
私が現在製作中のアプリケーションでは、レコードを更新する関数が多数定義されています。
これは、状態をとって、新しい状態を返す関数ですが、少し問題があります。
一つ例を示します。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(defrecord Cursor [x y]) | |
(defn right [n cursor] | |
(assoc cursor :x (+ (:x cursor) n))) | |
(defn down [n cursor] | |
(assoc cursor :y (+ (:y cursor) n))) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(defn right-down [n cursor] | |
(->> cursor | |
(right n) | |
(down n))) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(defn square [n cursor] | |
(let [cursor' (right n cursor)] | |
(down (:x cursor') cursor'))) |
さらに、この場合はxを取得するコストは微々たるものですが、フィールドの取得に複雑なロジックを用いたり、計算値が状態の更新に間接的に使われるだけで、レコードからは取得できない場合があります。
新しい状態だけを返すのではなく、計算値とのペアで返せばこのパターンを解決できます。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(defn right' [n cursor] | |
(let [x (+ (:x cursor) n)] | |
[(assoc cursor :x x) x])) |
そこでStateモナドの出番です!(導入長い!
Stateモナドは状態を取り、新しい状態と計算値のペアで返す関数に対してモナドを定義したものです。
先ほど定義したMonadicをStateに実装してみます。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(defrecord State [f] | |
Monadic | |
(fmap [s g] | |
(State. (fn [s] | |
(let [[s' a] (f s)] | |
[s' (g a)])))) | |
(bind [s g] | |
(State. (fn [s] | |
(let [[s' a] (f s)] | |
((:f (g a)) s')))))) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(defn right [n] | |
(State. (fn [cursor] | |
(let [x (+ (:x cursor) n)] | |
[(assoc cursor :x x) x])))) | |
(defn down [n] | |
(State. (fn [cursor] | |
(let [y (+ (:y cursor) n)] | |
[(assoc cursor :y y) y])))) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(defn square [n] | |
(do-m [x (right n) | |
s (down x)] | |
s)) |
関数の合成が手続き的に書けますね。
まとめ
マクロ強力。Stateモナド美しい。
本当はFreeモナド使おうと思ったけどStateモナドの素晴らしさを伝えたかったので控えました。
これから70個近い関数をStaeモナドに書き換える作業が待ってます。
Freeモナドの資料も作らなきゃですね。
私の代わりに大学のC言語の課題をやってくれるといいと思います。
0 件のコメント:
コメントを投稿