Arrow 入門
id:mzp さんに触発されて、Arrow 勉強中。
Haskell/Understanding arrows - Wikibooks, open books for an open worldの図が分かりやすいです。
まずは簡単なサンプルで動作を確認。
-- オリジナル f1 as = tail as ++ init as -- 引数を2つの経路に分けて、それぞれ tail と init を適用してから (++) でまとめる。 -- (&&&) の戻り値はタプルなので uncurry が必要。 f2 = arr (uncurry (++)) <<< tail &&& init -- 関数はそれ自体で Arrow のインスタンスなので、arr は不要。(!!) f3 = uncurry (++) <<< tail &&& init -- \ (f, a) -> f a は app で実現できる。 f4 = app <<< (++) . tail &&& init -- つまり下の2つの関数は等価 s1 a b = a <*> b s2 a b = app <<< a &&& b -- 関数を適用したい対象が1つではなく2つあれば -- (&&&) の代わりに (***) を使う。 -- (***) の第3引数はタプルなので curry が必要。 g1 as bs = tail as ++ init bs g2 = curry $ app <<< (++) . tail *** init -- first や second は (***) の特殊な場合 h1 as bs = tail as ++ bs h2 = curry $ app <<< first ((++) . tail)
これで昨日書いた関数が少しは簡単になるかな?
main = (app <<< (=<<) . runProg . genProg &&& liftM concat . getInputs) . parseOpt =<< getArgs
でも、これなら (<*>) で書いたほうがすっきりする。
main = ((=<<) . runProg . genProg <*> liftM concat . getInputs) . parseOpt =<< getArgs
いずれにしても (=<<) がカッコつきのまま残ってしまっているので、可読性はイマイチ。
(=<<) とか (<<<) は、中置演算子として使わないと、“右から左に”流れてくれないので、どうしても読みづらいですね。
そうすると、結局 do を使わないとダメか。
main = do args <- getArgs let opts = parseOpt args inputs <- getInputs opts runProg (genProg opts) (concat inputs)
ここまでだと Arrow のご利益はあまりないみたいだけど、Monad を絡めたり、loop を使ったりするとまた違うのかな。
また元気のあるときに調べよう。