KleisliでもArrowしたい。

Haskellの話。

Control.Arrow
というライブラリがあるのだが、自分に取っては
first, second, (***),(&&&)くらいを使うくらいのユーティリティライブラリでしかなかった。

class Category cat where
    id :: cat a a
    (.) :: cat b c -> cat a b -> cat a c

(>>>) :: Category cat => cat a b -> cat b c -> cat a c
a >>> b = b . a

class Category a => Arrow a where
    arr :: (b -> c) -> a b c
    first :: a b c -> a (b,d) (c,d)
    second :: a b c -> a (d,b) (d,c)
    (***) :: a b c -> a b' c' -> a (b,b') (c,c')
    (&&&) :: a b c -> a b c' -> a b (c,c')

(>>>)は射の合成、first,secondはそれぞれペアの第1要素、第2要素のみを変更する射、
(***)は射の積、(&&&)は一つのソースを二つに分割する射となっている。

最近モナドでfirst, secondを使いたくなるときがあったのでメモ。
具体的には次のようなコードだ

splitBy :: [a] -> Int -> [[a]]
splitBy [] _ = []
splitBy l  n = hd:splitBy tl n where
    (hd,tl) = splitAt n l

これはリーダーモナドを使って次のように書ける。

splitBy :: [a] -> Int -> [[a]]
splitBy [] = return []
splitBy l = do
    (hd,tl) <- flip splitAt l
    tl' <- splitBy tl
    return (hd:tl')

気持ち的には

splitBy l = fmap (uncurry (:)) $ flip splitBy l >>= second splitBy

のような感じで書きたいのだがsecondの型が合わない。
調べてみると

newtype Kleisli m a b = Kleisli { runKleisli :: a -> m b }
instance Monad m => Arrow (Kleisli m)

というinstanceを見つけたのでこれが使えそう。

splitBy :: [a] -> Int -> [[a]]
splitBy [] = return []
splitBy l = fmap (uncurry (:)) $ flip splitAt l >>= runKleisli (second (Kleisli splitBy))

これで型もあうのだが、>>=の右側がどうにも格好悪い。
他の例も考えてみてよい補助関数を見つけたいところだ。