Stephen Tetley がコメントで述べたように、その例は実際には文脈依存性を使用していません。コンテキスト依存について考える 1 つの方法は、モナドの値に応じてどのアクションを実行するかを使用者に選択させることです。アプリケーション計算は、含まれる値に関係なく、ある意味で常に同じ「形状」を持たなければなりません。モナド計算は必要ありません。個人的には具体例があるとわかりやすいと思いますので、一つ見てみましょう。以下は、パスワードの入力を求める単純なプログラムの 2 つのバージョンです。正しいパスワードを入力したかどうかを確認し、入力したかどうかに応じて応答を出力します。
import Control.Applicative
checkPasswordM :: IO ()
checkPasswordM = do putStrLn "What's the password?"
pass <- getLine
if pass == "swordfish"
then putStrLn "Correct. The secret answer is 42."
else putStrLn "INTRUDER ALERT! INTRUDER ALERT!"
checkPasswordA :: IO ()
checkPasswordA = if' . (== "swordfish")
<$> (putStrLn "What's the password?" *> getLine)
<*> putStrLn "Correct. The secret answer is 42."
<*> putStrLn "INTRUDER ALERT! INTRUDER ALERT!"
if' :: Bool -> a -> a -> a
if' True t _ = t
if' False _ f = f
これを GHCi にロードして、モナディック バージョンで何が起こるかを確認しましょう。
*Main> checkPasswordM
What's the password?
swordfish
Correct. The secret answer is 42.
*Main> checkPasswordM
What's the password?
zvbxrpl
INTRUDER ALERT! INTRUDER ALERT!
ここまでは順調ですね。しかし、適用可能なバージョンを使用する場合:
*Main> checkPasswordA
What's the password?
hunter2
Correct. The secret answer is 42.
INTRUDER ALERT! INTRUDER ALERT!
間違ったパスワードを入力しましたが、それでも秘密がわかりました! そして侵入者警報!これは、<$>
and <*>
、または同等の/が常にすべての引数の効果を実行するためです。適用可能なバージョンは、表記法で次のように変換されます。liftAn
liftMn
do
do pass <- putStrLn "What's the password?" *> getLine)
unit1 <- putStrLn "Correct. The secret answer is 42."
unit2 <- putStrLn "INTRUDER ALERT! INTRUDER ALERT!"
pure $ if' (pass == "swordfish") unit1 unit2
そして、なぜこれが間違った振る舞いをするのかは明らかです。実際、アプリカティブ ファンクターのすべての使用は、次の形式のモナド コードと同等です。
do val1 <- app1
val2 <- app2
...
valN <- appN
pure $ f val1 val2 ... valN
(一部はappI
の形式にすることが許可されていますpure xI
)。同様に、その形式のモナド コードは次のように書き換えることができます。
f <$> app1 <*> app2 <*> ... <*> appN
または同等に
liftAN f app1 app2 ... appN
これについて考えるには、Applicative
のメソッドを検討してください。
pure :: a -> f a
(<$>) :: (a -> b) -> f a -> f b
(<*>) :: f (a -> b) -> f a -> f b
次に、何Monad
が追加されるかを検討します。
(=<<) :: (a -> m b) -> m a -> m b
join :: m (m a) -> m a
(これらのうちの 1 つだけが必要であることを忘れないでください。)
よく考えてみると、アプリカティブ関数をまとめる唯一の方法は、形のチェーンを構築しf <$> app1 <*> ... <*> appN
、おそらくそれらのチェーンをネストすることです(たとえば、 、f <$> (g <$> x <*> y) <*> z
)。ただし、(=<<)
(or (>>=)
) を使用すると、値を取り、その値に応じて異なるモナド計算を生成できます。これは、その場で構築できます。これは、「秘密を出力する」を計算するか、「侵入者の警告を出力する」を計算するかを決定するために使用するものであり、アプリケーション ファンクターだけではその決定を行うことができない理由です。適用関数の型はどれも、単純な値を消費することを許可しません。
同様の方法で とjoin
連携して考えることができます:コメントで述べたように、次のようなことができますfmap
checkPasswordFn :: String -> IO ()
checkPasswordFn pass = if pass == "swordfish"
then putStrLn "Correct. The secret answer is 42."
else putStrLn "INTRUDER ALERT! INTRUDER ALERT!"
checkPasswordA' :: IO (IO ())
checkPasswordA' = checkPasswordFn <$> (putStrLn "What's the password?" *> getLine)
これは、値に応じて異なる計算を選択したいが、適用可能な機能しか利用できない場合に発生します。2 つの異なる計算を選択して返すことができますが、それらはアプリカティブ ファンクターの外側のレイヤー内にラップされています。選択した計算を実際に使用するには、次のものが必要ですjoin
。
checkPasswordM' :: IO ()
checkPasswordM' = join checkPasswordA'
import Control.Monad
そして、これは以前のモナド バージョンと同じことを行います (最初に を取得する限りjoin
):
*Main> checkPasswordM'
What's the password?
12345
INTRUDER ALERT! INTRUDER ALERT!