2013/08/18

Monad のようで Monad じゃないものを書いてみた

最近は すごいH本読んだり とか Haskell ってたのですが Monad が謎だったので試しに書いてみました。

結論から言えば Monad 則を満たしていないので、「Monad の instance にできてしまった何か」なのですが。
ってな訳で HookMonad なるものを作ってみた話。

HookMonad

a -> a な関数と a な値を持つデータ構造を定義。
>>= で関数を適用する際に a -> a の関数を適用してから関数を適用する、ってものにしてみました。

適用してみる想定として座標を考える。名前は nonMinusPoint とかにしてみました。
座標用のデータ構造で、値が 0 以下になったら 0 にしてくれる、って代物。
「プログラマブルセミコロン」のたとえを聞いて最初に思いついたものがコレ。
毎回 「0 以下なら……」 的なロジックが必要無さそうなので良いのかなー、と。
ソースはこんな感じ。

どう処理してるのか

Hook に a -> a な f として「0 以下なら 0 にする」lambdaを渡してます。
そうして >>= で関数を適用したい時はその lambda を適用してから関数を適用させる、と。
それを単純に使うために nonMinusPoint とかって名前を付けておく。
値を操作する関数 add とか multi には 「値が 0 かどうか」的なロジックはいらない。はず。
(とは言っても nonMinusPoint を結局使うので、毎回 lambda 書いてるのとあまり変わらない気もするけれど)
そうすると、 >>= で関数を適用する分には値が 0 以上になることが保証されそう。
あと、 Maybe の「失敗するかもしれない演算」みたいに「マイナスになるかもしれない演算」があったとしても確実に 0 以上であることが保証される。
だから add とかじゃなくて「0 以上でないと使えない関数」を定義した方が良いのかも。

 

ちなみにこれはMonadじゃない

ただし前述したけれどこれは Monad 則を満たしてません。
ちなみにMonad 則は以下。
return a >>= f ≡ f a
m >>= return ≡ m
(m >>= f) >>= g ≡ m >>= (\x -> f x >>= g)
1はどうにか満たしてるはず。
2は無理。return の型を合わせるために id を使ってるので、 m >>= return すると a -> a な関数が id に変わっちゃう。
3はどうだろ。たぶん満たしてそうだけれど反例が思いつかないだけかもしれない。

さらに問題点

もともとは「数値演算をしたら non zero にする」みたいなものを作りたかったので
Hook f n >>= g = fmap f $ g n
とかにしたかった。
だけれど >>= の型は
(>>=) :: Monad m => m a -> (a -> m b) -> m b
なので、 埋め込んでる a -> a な関数を m b に fmap しようとしてダメ、って言われる。
あと HookMonad が Functor じゃないと f を数値演算後に適用できないのだけれど、 fmap の型も
fmap :: Functor f => (a -> b) -> f a -> f b
ってなってるので、 Hook (a -> a) b になっちゃってダメ。
さらに、 return に id を使ってるので、汎用性を高めるために return を使ってるようなコードだとかえって a -> a な f が id になっちゃって埋め込んでたのが消えちゃう。
完全に Monad則 の 2 がダメになっています。
ってな訳でいろいろ問題点は多し。
ただ、「演算のたんびに何かする」ことはできたので良いのかなー、とか思っています。
まー、>>= に与える関数の中でも nonMinusPoint とか書いてるから「毎回何かする」部分を毎回書いちゃってる説もありますが。
(いやでも nonMinusPoint にすることで型が Hook になるので Hook なコードを書いてたら 型チェックされる ==  非ゼロ保証 だし良いのか? うーん)

なんか良いこと

あと書いててすごいなー、と思ったこと。
この Hook ですが deriving Show してません。
どうしてかと言えば関数を持ってるから deriving だと無理。
だから getValue を定義して、値だけを取り出すようにしないと ghci で値を見られませんでした。
その getValue の中でも f を適用するようにしたので、最後の最後だけ処理後に 0 以下かどうかのチェックができてます。
あと、コメントアウトしてるのは ghci で実行してみた例なのですが、 foldl でガンガン処理していけるっぽいです。
あと途中結果を見るために scanl を使ってみたのだけれどそのままだと見られない。
scanl は計算途中の内容を List にして返すので、その List に対して fmap で getValue かけたらきちんと見られました。
ちゃんとした Functor すごいなー、とか。
あと scanl で計算結果の途中を見ると、 0 以下になるところで 0 になってるっぽい。

ってなわけで Monad のようで Monad じゃない何かを書いてみました。

0 件のコメント:

コメントを投稿