モナド
概要
haskellclass 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
型の(純粋な)値として扱うことができるようになる。
haskellv :: Monad m => a -> m a
v x = compute x >>= return
where compute :: a -> m a
compute x = undefined
しかし
>>=
を多用すると
fp-tsの
chain
のように、いわゆるコールバック地獄になって見た目がよろしくない。
haskellvv :: (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
記法を使う。
haskellvv :: (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がある。
haskell1. 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
が返ってきたように書けるわけだな。
haskellenv :: [(String, Int)]
env = [("x", 3), ("y", 10)]
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
をつなげていく必要がある。
haskellfoo :: ()
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
? 知らない子ですね…。
忙しい人向け
モナドは単なる自己関手の圏におけるモノイド対象だよ。何か問題でも?