モナドが比喩で表せないことをわかりやすく説明したいモナドチュートリアル補足


厄介なのはjoinだ。joinは以下のような型を持つ。

join :: Monad m => m (m a) -> m a

読み下してみよう。「joinは、二重にネストしたモナド(m (m a))をとり、一つに潰して返す(m a)」。
モナドとは、このjoinによって決まる。
モナドの定義でreturn, (>>=)の対がよく出てくるが、(>>=)の代わりにjoinを使ってもいい。
(>>=)のjoinを用いた定義は以下だ。

(>>=)     :: Monad m => m a -> (a -> m b) -> m b
m >>= f   = join $ fmap f m

これも読んでみよう。「bind(>>=)は、(fmap f m)で生成されたネストされたモナド(m (m b))を、joinで潰した結果を返す」


つまりモナドの性質とは、このjoinの、モナドの潰し方によって決まる。
つまりモナドとは、このjoinの具体的な実装によってベルトコンベアにもシュークリームにも象にも接ぎ木にもベドウィンにもなるということだ。


問題はこのjoin、「ネストを潰して一つにする」という部分をリアルワールドで上手く例えられる概念が存在しないのだ。
だからモナドの比喩は失敗する。
何か見つかった方は、それを使ってうまくモナドを例えて欲しい。僕は思いつかなかった。


プログラムの結果


関数は次のような形をしている。

a -> b

入力aに対し、bが返る。
対してプログラムとは次の様な形をしている。

a -> m b

入力aに対し、単なるbではなく、(m b)が出力として返る。
m bとは何か。プログラムの結果だ。ということをMoggiさんが考えた。らしい。

ということをwikipediaモナドのページをみてへーって思った。

僕らはこのプログラムを組み合わせたい。ということは他のエントリで何度も書かれているのでここでは置いておこう。

さて問題は(m a)といった形式だと思う。Haskellで頻出する(m a)はとても抽象的な表現だ。
Haskell入門者はこの(m a)から具体的な値が想像出来ない。mもaも型パラメータである。なんだこれは。抽象的すぎて意味が分からない、となるわけだ。僕はなった。


この(m a)を、「型aを所持したmというコンテナ」と解釈してしまうと、理解の壁にぶつかるかもしれない。
例えば(m a)は次のような形をしている。

data Maybe a = Nothing | Just a

例えば(m a)は次のような形をしている。

data List a = Nil | Cons a (List a)

例えば(m a)は次のような形をしている。(State s)の部分がmだ。

newtype State s a = State { runState :: s -> (a, s) }

例えば(m a)は次のような形をしている。

newtype Writer w a = Writer { runWriter :: (a, w) }

例えば(m a)は次のような形をしている。

newtype Cont r a = Cont { runCont :: (a -> r) -> r }

例えば(m a)は次のような形をしている。

data Free f a = Pure a | Free (f (Free f a))

例えば(m a)は次のような形をしている。

data Pipe l i o u m a =
    HaveOutput (Pipe l i o u m a) (m ()) o
  | NeedInput (i -> Pipe l i o u m a) (u -> Pipe l i o u m a)
  | Done a
  | PipeM (m (Pipe l i o u m a))
  | Leftover (Pipe l i o u m a) l

aを含んだコンテナというより、aは単なるパラメータと考えた方がいいかもしれない。
少しややこしい事に、型族による型関数のために同じような見た目になっていることがある。よく見れば気づくだろうけど、気をつけて。


具体的なモナドを見かけたら、その型とjoinを確認するといいかもしれない。