Life Goes On

まあまあだけど楽しんでる方です

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 を使ったりするとまた違うのかな。
また元気のあるときに調べよう。