40

この記事を読みましたが、最後のセクションが理解できませんでした。

著者は、Monad は文脈依存性を提供すると述べていますが、Applicative インスタンスのみを使用して同じ結果を達成することは可能です。

let maybeAge = (\futureYear birthYear -> if futureYear < birthYear
    then yearDiff birthYear futureYear
    else yearDiff futureYear birthYear) <$> (readMay futureYearString) <*> (readMay birthYearString)

do 構文がないと確かに醜いですが、それ以外に Monad が必要な理由がわかりません。誰かが私のためにこれをクリアできますか?

4

8 に答える 8

64

インターフェイスを使用するいくつかの関数を次に示しMonadます。

ifM :: Monad m => m Bool -> m a -> m a -> m a
ifM c x y = c >>= \z -> if z then x else y

whileM :: Monad m => (a -> m Bool) -> (a -> m a) -> a -> m a
whileM p step x = ifM (p x) (step x >>= whileM p step) (return x)

インターフェイスでそれらを実装することはできませんApplicative。しかし、理解を深めるために、どこがうまくいかないか見てみましょう。どうですか..

import Control.Applicative

ifA :: Applicative f => f Bool -> f a -> f a -> f a
ifA c x y = (\c' x' y' -> if c' then x' else y') <$> c <*> x <*> y

いいね!それは正しいタイプを持っています、それは同じものでなければなりません! 念のため確認しておきましょう..

*Main> ifM (Just True) (Just 1) (Just 2)
Just 1
*Main> ifM (Just True) (Just 1) (Nothing)
Just 1
*Main> ifA (Just True) (Just 1) (Just 2)
Just 1
*Main> ifA (Just True) (Just 1) (Nothing)
Nothing

そして、違いの最初のヒントがあります。Applicativeをレプリケートするインターフェイスだけを使用して関数を作成することはできませんifM

これを「効果」と「結果」に関するものとしてフォームの値を考えることに分割するとf a(どちらも非常にあいまいな近似用語であり、利用可能な最良の用語ですが、あまり良くありません)、ここで理解を深めることができます。 . type の値の場合Maybe a、「効果」は計算としての成功または失敗です。「結果」はa、計算が完了したときに存在する可能性があるタイプの値です。(これらの用語の意味は具象型に大きく依存するため、これが型以外の有効な説明であるとは思わないでくださいMaybe。)

その設定を考えると、違いをもう少し詳しく見ることができます。インターフェイスではApplicative、「結果」制御フローを動的にすることができますが、「効果」制御フローを静的にする必要があります。式に失敗する可能性のある 3 つの計算が含まれている場合、そのうちの 1 つでも失敗すると、計算全体が失敗します。インターフェイスはMonadより柔軟です。これにより、「効果」の制御フローが「結果」の値に依存するようになります。ifM最初の引数に基づいて、どの引数の「効果」を独自の「効果」に含めるかを選択します。ifAこれがとの根本的な大きな違いifMです。

にはさらに深刻な事態が進行中whileMです。whileA何が起こるか見てみましょう。

whileA :: Applicative f => (a -> f Bool) -> (a -> f a) -> a -> f a
whileA p step x = ifA (p x) (whileA p step <*> step x) (pure x)

まあ..何が起こるかはコンパイルエラーです。(<*>)そこに正しいタイプがありません。whileA p stepは typea -> f astep x持ち、 type を持ちf aます。(<*>)それらを組み合わせるのに適した形状ではありません。それが機能するには、関数の型が である必要がありますf (a -> a)

さらに多くのことを試すことができますが、最終的にはwhileA、その方法に近いものでも機能する実装がないことがわかりますwhileM。つまり、型を実装することはできますが、ループと終了の両方を行う方法はありません。

それを機能させるには、またはのいずれかが必要です。(まあ、またはそれらの1つに相当する多くの1つ)そして、これらはインターフェースから得られる余分なものです。join(>>=)Monad

于 2013-07-01T20:20:54.897 に答える
26

モナドでは、後続の効果は前の値に依存する可能性があります。たとえば、次のことができます。

main = do
    b <- readLn :: IO Bool
    if b
      then fireMissiles
      else return ()

sでそれを行うことはできませんApplicative- 1 つの effectfull 計算の結果値は、その後に続く効果を決定できません。

やや関連:

于 2013-07-01T16:44:05.657 に答える
25

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 <*>、または同等の/が常にすべての引数の効果を実行するためです。適用可能なバージョンは、表記法で次のように変換されます。liftAnliftMndo

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!
于 2013-07-01T19:42:34.717 に答える
11

一方、 s が有利なApplicative/Monad除算の実用的な例を次に示しApplicativeます。エラー処理です。エラーを伴うのMonad実装があることは明らかですが、常に早期に終了します。Either

Left e1 >> Left e2    ===   Left e1

これは、価値観と文脈が混ざり合った効果と考えることができます。は値の結果を のような関数(>>=)に渡そうとするため、入力がの場合はすぐに失敗する必要あります。Either e aa -> Either e bEitherLeft

Applicatives は、すべての効果を実行した後にのみ、最終的な純粋な計算に値を渡します。これは、値へのアクセスをより長く遅らせることができることを意味し、これを書くことができます。

data AllErrors e a = Error e | Pure a deriving (Functor)

instance Monoid e => Applicative (AllErrors e) where
  pure = Pure
  (Pure f) <*> (Pure x) = Pure (f x)
  (Error e) <*> (Pure _) = Error e
  (Pure _) <*> (Error e) = Error e
  -- This is the non-Monadic case
  (Error e1) <*> (Error e2) = Error (e1 <> e2)

エラーとそれらの両方を取得するために、値を使用する前に最初と 2 番目のコンテキストの両方を実行することを利用するため、そのような一致のMonadインスタンスを作成することは不可能です。icであり、それらの値が織り交ぜられたコンテキストにのみアクセスできます。そのため、のインスタンスは左に偏っているため、調和のとれたインスタンスを持つこともできます。AllErrorsap(<*>)(<*>)(<>)Monad(>>=)(join)EitherApplicativeMonad

> Left "a" <*> Left "b"
Left 'a'

> Error "a" <*> Error "b"
Error "ab"
于 2013-07-02T15:21:40.330 に答える
7

Applicative では、実行される効果的なアクションのシーケンスはコンパイル時に固定されます。Monad を使用すると、エフェクトの結果に基づいて実行時に変更できます。

たとえば、Applicative パーサーでは、解析アクションのシーケンスは常に固定されています。つまり、「最適化」を実行できる可能性があります。一方、BNF 文法記述の一部を解析し、その文法のパーサーを動的に構築し、残りの入力に対してそのパーサーを実行するモナディック パーサーを作成することもできます。このパーサーを実行するたびに、入力の 2 番目の部分を解析するための新しいパーサーが作成される可能性があります。Applicative にはそのようなことを行う見込みはありません。また、まだ存在しないパーサーでコンパイル時の最適化を実行する可能性はありません...

ご覧のとおり、Applicative の「制限」が実際に有益な場合もあれば、仕事を成し遂げるために Monad が提供する追加のパワーが必要な場合もあります。これが、両方を持っている理由です。

于 2013-07-02T07:45:21.427 に答える
5

Applicatives を使用する場合、結果の「形状」は入力の「形状」によってすでに決定されています。たとえば、 を呼び出すと[f,g,h] <*> [a,b,c,d,e]、変数の値に関係なく、結果は 15 要素のリストになります。モナドにはこの保証/制限はありません。考慮してください[x,y,z] >>= join replicate[0,0,0]あなたは結果を得るでしょう、結果のため[]に。[1,2,3][1,2,2,3,3,3]

于 2013-07-02T07:37:36.233 に答える
5

Monadbindと Applicativeの型シグネチャ<*>を自然言語に変換しようとすると、次のことがわかります。

bind:はあなたに含まれている値を渡し、あなたは私に新しいパッケージされた値を返します

<*>:含まれている値を受け入れて値を返すパッケージ化された関数を提供してください。それ使用して、ルールに基づいて新しいパッケージ化された値を作成します。

上記の説明からわかるようにbind<*>

于 2013-07-02T05:03:30.447 に答える
1

拡張機能がかなり一般的なものになった今、単純なコード スニペットを使用して とApplicativeDoの違いをMonad説明Applicativeできます。

あなたMonadとできる

do
   r1 <- act1
   if r1
        then act2
        else act3

しかし、do-block しかないので、で抜いたものにはApplicative使えません。if<-

于 2019-07-20T15:18:57.733 に答える