Haskell
Monad

任意の数の引数を取れる関数のデータ型を考えてたらモナドになった

func1とfunc2を同じ型にしたい。

func1 :: r -> a

func2 :: r -> r -> a

リストで引数渡したら実行できるといいな。

引数が足りなかったらエラーにでもしようかな。

data ManyArgsFunc r a = ????

run :: ManyArgsFunc r a -> [r] -> a

Haskellといえば再帰

data ManyArgsFunc r a = Pure a | Func (r -> ManyArgsFunc r a)

func1とfunc2を変換できるか確認

あと実行関数が思い通りになるか

toManyArgsFunc1 :: (r -> a) -> ManyArgsFunc r a

toManyArgsFunc1 func1 = Func $ \r -> Pure (func1 r)

toManyArgsFunc2 :: (r -> r -> a) -> ManyArgsFunc r a
toManyArgsFunc2 func2 = Func $ \r1 ->
Func $ \r2 -> Pure (func2 r1 r2)

-- リストの引数が関数に順番に渡って、Pureまでくれば値を返す。
-- 途中で引数がなくなったらエラー
run :: ManyArgsFunc r a -> [r] -> a
run (Pure a) _ = a
run (Func f) (r:rs) = run (f r) rs

うまくいった。:relaxed:

ここで思った。

これ、Freeモナドに似てるのでモナドになるのでは。

instance Functor (ManyArgsFunc r) where

fmap f (Pure a) = Pure (f a)
fmap f (Func g) = Func $ \r -> fmap f (g r)

instance Applicative (ManyArgsFunc r) where
pure a = Pure a
(Pure f) <*> x = fmap f x
(Func g) <*> x = Func $ \r -> (g r) <*> x

instance Monad (ManyArgsFunc r) where
return a = Pure a
(Pure a) >>= f = f a
(Func g) >>= f = Func $ \r -> (g r) >>= f

やったーモナドになったー

挙動としてはこんなかんじ

func1 :: ManyArgsFunc Int String

func1 = Func $ \i -> Func $ \j -> Pure $ show (i + j)

trace :: (Show r , Show a) => ManyArgsFunc r a -> [r] -> String
trace (Pure a) _ = "Pure " ++ show a
trace (Func f) (r:rs) = "Func $ " ++ show r ++ " -> " ++ trace (f r) rs

action :: ManyArgsFunc Int (String,String)
action = do
a <- func1
b <- func1
return (a,b)

>>> run action [1..]
("3","7")

>>> trace action [1..]
Func $ 1 -> Func $ 2 -> Func $ 3 -> Func $ 4 -> Pure ("3","7")

なにか役に立つ使い方が思いつけばまた記事にします。

もし、すでにこれと同じ構造のモナドのパッケージがあれば教えてください