日記 (2019 年 3 月 11 日)
Functor
は関手だし Monad
は文脈付きの値を扱う構造ということで、 この 2 つは何となく気持ちが掴めてきたんですが、 その間にある Applicative
の気持ちがよく分かりませんでした。
すると、 Twitter で hsjoihs さんから 「前の結果に依存して計算を変えるという Monad
の機能を取り除いたのが Applicative
」 という捉え方があると教えてもらいました。
これを聞いたときは分かったような分からないようなという感じでしたが、 だんだん分かってきたのでまとめてみます。
Monad
には do 構文という専用のシンタックスシュガーがあって、 あたかも手続き型かのように処理を書くことができます。
例えば、 以下のような関数を作ることができます。
f
と g
があるモナド m
上のアクションで、 h
は普通の関数です。
mofu x = do
y <- f x
z <- g x y -- !!
return $ h y z
これは以下のような式に脱糖されます。
Monad
クラスで定義されている >>=
が使われていますね。
分かりやすいように括弧を書いていますが、 結合性の仕様から実際は不要です。
mofu x = f x >>= (\y -> g x y >>= (\z -> return $ h y z))
ここで注目すべきは、 最初の do 構文を使った表現の 3 行目において、 z
が y
に依存しているところです。
y
というのはその前の計算 (f
の実行) で得られた値なので、 まさに前の結果に依存して計算が変わっているわけです。
仮に g
が第 2 引数を取らず、 z
の計算のとき y
に依存していなかったらどうでしょうか。
つまり、 以下のような処理を考えるわけです。
mofu x = do
y <- f x
z <- g x -- y に依存させない
return $ h y z
これも、 同じように以下のように脱糖されます。
mofu x = f x >>= (\y -> g x >>= (\z -> return $ h y z))
しかし、 これは以下の式と等価です。
mofu x = h <$> f x <*> g x
<$>
は Functor
クラスで定義されていて、 <*>
は Applicative
クラスで定義されているので、 上の式を書く分には m
が Monad
のインスタンスである必要はなく、 Applicative
のインスタンスで十分です。
つまり、 アクションを実行した結果として何らかの値を取り出すときに、 アクションの引数として前の実行結果の値を使っていなければ、 >>=
を使わずに <$>
と <*>
だけで済ませられるわけです。
これが、 「前の結果に依存して計算を変えるという Monad
の機能を取り除いたのが Applicative
」 という言明の正体です。
これは、 各演算子のシグネチャを見ることでも分かります。
(<$>) :: Functor f => (a -> b) -> f a -> f b
(<*>) :: Applicative f => f (a -> b) -> f a -> f b
(>>=) :: Monad m => m a -> (a -> m b) -> m b
>>=
は、 第 1 引数として受け取るのは m a
型の値なのに、 第 2 引数で受け取る変数の始域は a
です。
こうなっているおかげで、 文脈付きの値である m a
型の値が、 あたかも普通の a
型の値であるかのように関数に渡し、 その結果を得ることができるわけです。
しかし、 <$>
と <*>
ではどちらも、 このような文脈付きの値を普通の値として渡せるという状況は起きていません。
そのため、 この 2 つだけでは以前のアクションの結果を次のアクションに渡すことはできず、 前の文脈に応じて実行内容を変えることはできないのです。