定義に立ち戻って確認すればわかるんだけど、直感的には理解できていなかったのでメモしておく。
こういうdo記法を考える(計算結果はm d
型の値になる):
do
x <- A -- A::(m a)
y <- B -- B::(m b)
z <- C -- C::(m c)
return (f x y z) -- f::(a->b->c->d)
もしC
がx
やy
の値を利用しているとすると(つまり前の計算による束縛を利用しているとすると)、余計な引数のない一番単純な場合でC
はa -> b -> m c
型の函数g
によってB = g x y
と書けることになる。
さて、ここでjoin
が使えない非MonadなApplicativeでこのdo記法と同じ計算を実現しようとするとどうなるか。まず、g
とApplicativeの道具を使ってできるのはreturn g <*> A <*> B
まででしかない。だがreturn g
はm (a -> b -> m c)
型なので、return g <*> A
の型はm (b -> m c)
で、もう一度<*>
を使ってm
の中に適用してやっても、その結果として出来上がる値の型はm (m c)
になってしまう。これでは函数f
で m d
型の値を返してくることはできない。どうがんばってreturn
や<*>
を多重に使ったりしても、欲しいのはm d
型の値なのに得られるのはせいぜいm (m d)
型の値になってしまう。
もちろん、join
が使えればこれらのm
の入れ子は殆どただちに外せるが、非MonadなApplicativeにはまさにそれが使えないわけだ。逆に言えば、join
を使わずに済ませるにはC
がx
やy
を利用していなければいいので(もちろんB
がx
を利用していてもいけない)、その場合には上のdo記法の計算は:
(\x y z -> f x y z) <$> A <*> B <*> C
というApplicativeスタイルの計算に等価になる。
つまり、 連鎖の途中で前の計算結果を参照するようなdo記法の計算はApplicativeでは実現できず、連鎖の途中で前の計算結果を参照しないようなdo記法の計算はApplicativeに解糖できる 。これがつまり、「前の計算の結果に応じて後の計算の挙動を変えるという『条件分岐』が非MonadなApplicativeにはできずMonadにはできる」ということの中身。
Applicativeにできるのはdo記法の中でお互いに交わらない計算を複数進めていって最後の最後にそれらをまとめ上げること、というわけだが、既視感があるのも当たり前で、これって要するに(->)
Applicativeがやってたことだよな。というか(->)
Applicativeは、交わらない複数の計算を最後にまとめ上げるというApplicative本来の作用に対して、これらの複数の計算に同じ引数を投入するという制約を課す特殊化をしたものにほかならない。
ただ、m
が一方的に入れ子になったりモナド則が保証しているような素性の良いものではない振る舞いをしてもいいなら途中の計算結果を使って挙動を変えること自体はできるはずだが、そうするとフラットに「逐次」「反復」「分岐」を組み合わせられなくなるから結局意味なしという辺りなんだろう、きっと(情並感)。