Arrowの柔軟性
id:MaD:20070820で書いたmapAの問題点ですが、ArrowLoopはそれを解決してくれるものではありませんでした。IOモナドやMonadFixについて勉強不足なので、これらについてしっかりと理解出来てはいないです。
問題が起こるのは、IOモナドとかStateモナドとか副作用のあるモナドを使用する場合でリストモナドやMaybeモナドなどを使用する場合は大丈夫です。
ただ、Monadに比べるとインターフェースが無駄に柔軟なので、無意識的にモナドが再帰されたコードが出来てしまいやすいので注意しないといけなさそうです。pure,arrを書く位置がちょっと変わっただけで遅延評価が行われない反応の悪いプログラムになってしまうなどが起こってしまいます。
Arrowがどれだけ柔軟かという例を少し書いてみます。
(Kleisliといちいち書くのが面倒なので自分はKleisli -> K, runKleisli -> runKと置き換えて書いています。Kleisli.hs)
まず適当に+ 1するArrowを用意します
plus1 :: (Arrow a, Num b) => a b b plus1 = pure (+ 1)
これは普通の関数として使えます。また普通の関数もArrowです。
> plus1 1 2 > show.plus1 $ 3 "4" > map plus1 [1, 2, 3, 4, 5] [2,3,4,5,6] > (plus1 >>> show) 3 "4"
各種Arrowに繋ぐと自動的に変換されます。
Kleisli IO型に繋ぐ
> runK (plus1 >>> K print) 1 2 > runK (arr read >>> plus1 >>> K print) "3" 4
Kleisli Maybe型に繋ぐ
> let test = [("one", 1), ("two", 2), ("three", 3)] > runK (K (`lookup` test) >>> plus1) "two" Just 3 > runK (K (`lookup` test) >>> plus1) "three" Just 4 > runK (K (`lookup` test) >>> plus1) "four" Nothing
lookupで検索した結果をちょっと整形したいとか言う場合に便利かもしれないです。
Kleisli []型に繋ぐ
> runK (K (return [1, 2, 3, 4, 5]) >>> plus1) () [2,3,4,5,6] > runK (K (\x -> [x,-x]) >>> K (\x -> [x, x*2]) >>> plus1) 1 [2,3,0,-1]
リストモナドと同じ挙動をします。普通の関数をpureでKleisli []に変換すると見た目上はmap動作になります。
Monadのコードと比較してみる。
適当に選んだふつけるのMonadを利用するコードと比較してみます。
countline.hs
IOモナド版
main = do cs <- getContents print $ length $ lines cs
Kleisli IO版
main = runK (K (const getContents) >>> pure lines >>> pure length >>> K print) ()
これでもいいですが、
自分は次の書き方の方がいいんじゃないかと思います。
main = getContents >>= (lines >>> length >>> print)
IOモナドの記法をベースにして関数合成の部分だけArrowの記法を使っている書き方です。
理由は
- runKとかKとかは見にくい。(runKleisliとKleisliならなおさら)
- KでArrowにして、runKでモナドに戻すという無駄がない。
- 通常の関数を無駄にモナド化するのはどうかと思う。
- モナドが知らず知らずのうちに再帰される問題は減る。
もう一つこの書き方で書いてみます。
tail.hs
main = do cs <- getContents putStr $ lastNLines 10 cs lastNLines n cs = unlines $ takeLast n $ lines cs takeLast n ss = reverse $ taken n $ reverse ss
main = getContents >>= (lastNLines 10 >>> putStr) lastNLines n = lines >>> takeLast n >>> unlines takeLast n = reverse >>> take n >>> reverse
このくらいなら見やすいかもしれません。
Kleisliについては、本来Monadにも(>>=)や(>>)などによって記法を改善するという目的があるわけですから、わざわざArrow化するメリットはないんじゃないかという感想です。
mapAなどのArrow用の汎用アルゴリズムを使ったり、コマンドコンビネータなどの記法を使いたい場合には必要になってくると思います。