モナド
概要
code:haskell
class Applicative m => Monad m where
return :: a -> m a
(>>=) :: m a -> (a -> m b) -> m b
計算エフェクトmがついているa型の値を扱うときに便利。
(>>=) :: m a -> (a -> m b) -> m b の左辺に値m aを入れると、右辺の関数a -> m b内ではa型の(純粋な)値として扱うことができるようになる。
code:haskell
v :: Monad m => a -> m a
v x = compute x >>= return
where compute :: a -> m a
compute x = undefined
しかし>>=を多用すると fp-tsのchainのように、いわゆるコールバック地獄になって見た目がよろしくない。 code:haskell
vv :: (Monad m, Num b) => b -> b -> b -> m b
vv x y z = compute x >>=
\x' -> compute y >>=
\y' -> compute z >>=
\z' -> return $ x' + y' + z'
そこでdo記法を使う。
code:haskell
vv :: (Monad m, Num b) => b -> b -> b -> m b
vv x y z = do
x' <- compute x
y' <- compute y
z' <- compute z
return $ x' + y' + z'
計算エフェクトmがついているa型の値を扱うときに、do記法で手続き的に(あるいは具体的な構造をモナドのインスタンスによって隠蔽して)処理を書けるので便利。
モナド則
モナドとは圏論から来ている言葉で、圏論は代数の知り合いである。
モナドにもequation rulesがある。
code:haskell
1. return x >>= f === f x
2. m >>= return === m
3. (m >>= f) >>= g === m >>= (\x -> f x >>= g)
これはモナドが持っておくべき性質だが、これを知らないと死んだり知らないでHaskellを書くとOSがクラッシュするなどの事態にはならない。
計算エフェクト とはなんですか?
多くの誤解を生むが広く知られている言葉でいえば 副作用 です。
ある文脈では m が Maybe だったり、またあるときは IO になる。
なるほど、 lookup :: [(String, a)] -> String -> Maybe a などのありふれた関数が返す値 Maybe a というのを 副作用として扱いたいときにモナドがあると、あたかも Maybe a ではなく a が返ってきたように書けるわけだな。
code: haskell
evaled :: Maybe Int
evaled = do -- Monad Maybe インスタンスをガンガン活用する
x <- lookup env "x"
y <- lookup env "y"
z <- lookup env "z" -- Nothing
return (x + y + z) -- だが何事もなかったかのように記述できる
やれモナドだ副作用だ純粋だと言われるときによく槍玉に挙げられるIO だが、これもただたんに IO :: * -> * という装飾の付いた値が関数から返ってくるので、それを手続き的に書きたいがためにモナドを利用しているに過ぎない。
IO モナド
IO は単なる型 * → * に過ぎないと書いたが、実はそうでもない。というのも IO の表すエフェクトはファイル等の入出力や IORef に見られるメモリの参照など、処理系レベルでなんとかしないといけない操作である。 IO モナドのエンドポイントは 関数main :: IO ()であり、 mainまでIOをつなげていく必要がある。
code:haskell
foo :: ()
foo =
let _ = putStrLn "Hello" -- この式のIOの文脈を無視
in () -- しかも無を返す
bar :: IO ()
bar = do
_ <- putStrLn "World" -- 冗長ですが
return () -- IO の文脈を継いでいる
biz :: IO ()
biz = do
let _ = putStrLn "!" -- この式のIOの文脈を無視して
putStrLn "*" -- こちらを返す
main :: IO ()
main = do
_ <- return foo -- nothing to do
_ <- bar -- prints World
_ <- biz -- prints only *
return ()
例えば上のコードではfooは内部でIOを発生させようとしているが()を返しており、IOの文脈は無視されている。bizはIO ()を返すが最初のputStrLnはただのletで束縛しており、IOの文脈が断絶している。barではmonadic bindでputStrLn "World"を束縛しているため、次のreturn ()にIOの文脈がつながっている。このように文脈をつなげて書いていくプログラミングスタイルをmonadic programmingと呼ぶ。特にHaskellではこう書かないとIOエフェクトを発生させることができない。unsafePerformIO ? 知らない子ですね…。
忙しい人向け
モナドは単なる自己関手の圏におけるモノイド対象だよ。何か問題でも?