Monad
とApplicative
の違いについて、 typeclassopedia から以下を読みました。 join
にはApplicative
がないことを理解できます。しかし、次の説明は私にはあいまいに見え、私はモナド計算/アクションの「結果」が何を意味するのかを正確に理解できませんでした。それで、もしモナドを作るMaybe
に値を入れると、この「計算」の結果はどうなりますか?
(>> =)のタイプをさらに詳しく見てみましょう。基本的な直感は、2つの計算を1つの大きな計算に結合することです。最初の引数m aは、最初の計算です。ただし、2番目の引数が単なるm bの場合は退屈です。計算が相互に作用する方法はありません(実際、これはまさにApplicativeの状況です)。そのため、(>> =)の2番目の引数のタイプはa-> m bです。このタイプの関数は、最初の計算の結果が与えられると、実行する2番目の計算を生成できます。 ...直感的に、MonadをApplicativeよりも強力にするのは、前の計算の出力を使用して次に実行する計算を決定する機能です。 Applicative計算の構造は固定されていますが、Monad計算の構造は中間結果に基づいて変更できます。
「前の計算からの出力を使用して、次に実行する計算を決定する能力」を示す具体的な例はありますか?
私のお気に入りの例は、「純粋に適用可能などちらか」です。いずれかの基本Monadインスタンスを分析することから始めます
instance Monad (Either e) where
return = Right
Left e >>= _ = Left e
Right a >>= f = f a
このインスタンスは非常に自然な短絡の概念を埋め込みます。左から右に進み、1つの計算がLeft
に「失敗」すると、残りのすべても同様に失敗します。 Applicative
が持つ自然なMonad
インスタンスもあります
instance Applicative (Either e) where
pure = return
(<*>) = ap
ここで、ap
はreturn
の前の左から右への順序にすぎません。
ap :: Monad m => m (a -> b) -> m a -> m b
ap mf ma = do
f <- mf
a <- ma
return (f a)
計算のどこかで発生し、何らかの形でエラーの要約を生成するエラーメッセージを収集する場合、このEither
インスタンスの問題が明らかになります。これは、短絡に直面して飛ぶ。また、(>>=)
のタイプに直面します
(>>=) :: m a -> (a -> m b) -> m b
m a
を「過去」と見なし、m b
を「未来」と見なす場合、(>>=)
は、「ステッパー」を実行できる限り、過去から未来を生成します(a -> m b)
。この「ステッパー」は、a
の値が将来本当に存在することを要求します...そして、これはEither
では不可能です。したがって、(>>=)
要求短絡。
その代わり、対応するApplicative
を持つことのできないMonad
インスタンスを実装します。
instance Monoid e => Applicative (Either e) where
pure = Right
(<*>)
の実装は、慎重に検討する価値のある特別な部分です。最初のの場合はある程度の「短絡」を行いますが、4番目の場合は興味深いことを行います。
Right f <*> Right a = Right (f a) -- neutral
Left e <*> Right _ = Left e -- short-circuit
Right _ <*> Left e = Left e -- short-circuit
Left e1 <*> Left e2 = Left (e1 <> e2) -- combine!
左の引数を「過去」、右の引数を「未来」と考えると、(<*>)
は未来を「開く」ことが許可されているため、(>>=)
と比較して特別であることに注意してください。 「未来」を計算するために「過去」からの結果を必ずしも必要とするのではなく、過去と並行して。
つまり、チェーンにApplicative
sが存在する場合はEither
sを無視して、純粋にRight
Left
を使用してエラーを収集できることを直接意味します。
> Right (+1) <*> Left [1] <*> Left [2]
> Left [1,2]
それでは、この直感を頭で反転させましょう。純粋に適用可能なEither
では何ができないのでしょうか?まあ、その動作は過去を実行する前に未来を調べることに依存しているため、過去の値に依存することなく未来の構造を決定できなければなりません。言い換えれば、私たちは書くことができません
ifA :: Applicative f => f Bool -> f a -> f a -> f a
次の方程式を満たします
ifA (pure True) t e == t
ifA (pure False) t e == e
ifM
と書くことができますが
ifM :: Monad m => m Bool -> m a -> m a -> m a
ifM mbool th el = do
bool <- mbool
if bool then th else el
そのような
ifM (return True) t e == t
ifM (return False) t e == e
ifA
は、引数計算に埋め込まれた値に応じて結果計算の概念を正確に具体化するため、この不可能性が生じます。
Just 1
は「計算」を表し、その「結果」は1です。Nothing
は、結果を生成しない計算を表します。
MonadとApplicativeの違いは、Monadには選択肢があるということです。 Monadsの主な違いは、計算で異なるパスを選択できることです(早期にブレークアウトするだけではありません)。計算の前のステップで生成された値に応じて、計算構造の残りの部分が変更される可能性があります。
これが何を意味するかです。モナド連鎖
return 42 >>= (\x ->
if x == 1
then
return (x+1)
else
return (x-1) >>= (\y ->
return (1/y) ))
if
は、構築する計算を選択します。
Applicativeの場合、
pure (1/) <*> ( pure (+(-1)) <*> pure 1 )
すべての関数は「内部」計算で動作するため、チェーンを分割することはできません。各関数は、渡された値を変換するだけです。計算構造の「形状」は、関数の観点からは完全に「外側」にあります。
関数は失敗を示す特別な値を返すことができますが、計算の次のステップをスキップすることはできません。それらはすべて、特別な方法でも特別な値を処理する必要があります。計算の形状は、受け取った値に応じて変更できません。
モナドでは、関数自体が選択に応じて計算を構築します。
これが私の@Jの見解です。 ifA
が内部で値を使用できない理由に関するAbrahamsonの例_(pure True)
_。本質的には、join
にMonad
からApplicative
関数が存在しないことに変わりはありません。これは、 typeclassopedia で指定された2つの異なる観点を統合して、Monad
とApplicative
の違いを説明します。
@Jを使用します。アブラハムソンの純粋に適用可能なEither
の例:
_instance Monoid e => Applicative (Either e) where
pure = Right
Right f <*> Right a = Right (f a) -- neutral
Left e <*> Right _ = Left e -- short-circuit
Right _ <*> Left e = Left e -- short-circuit
Left e1 <*> Left e2 = Left (e1 <> e2) -- combine!
_
(Either
Monad
と同様の短絡効果があります)、およびifA
関数
_ifA :: Applicative f => f Bool -> f a -> f a -> f a
_
上記の方程式を達成しようとした場合:
_ifA (pure True) t e == t
ifA (pure False) t e == e
_
?
さて、すでに指摘したように、最終的に_(pure True)
_の内容は後の計算で使用できません。しかし、技術的に言えば、これは正しくありません。 Monad
はFunctor
を持つfmap
でもあるため、canは_(pure True)
_のコンテンツを使用できます。我々はできる:
_ifA' b t e = fmap (\x -> if x then t else e) b
_
問題は、f (f a)
である_ifA'
_の戻り型にあります。 Applicative
では、ネストされた2つのApplicative
Sを1つにまとめる方法はありません。しかし、この折りたたみ関数は、join
のMonad
が実行するものです。そう、
_ifA = join . ifA'
_
ifA
を適切に実装できれば、join
の方程式を満たします。ここでApplicative
が欠落しているのは、まさにjoin
関数です。つまり、Applicative
の前の結果の結果を何らかの方法で使用できます。ただし、Applicative
フレームワークでこれを行うには、戻り値の型をネストされた適用可能な値に拡張する必要があります。これは、単一レベルの適用可能な値に戻す手段がありません。たとえば、Applicative
Sを適切に使用して関数を構成できないため、これは深刻な問題になります。 join
を使用すると問題は解決しますが、join
を導入すると、Applicative
がMonad
にプロモートされます。
違いの鍵は、ap
のタイプと__=<<
_のタイプで確認できます。
_ap :: m (a->b) -> (m a->m b)
=<< :: (a->m b) -> (m a->m b)
_
どちらの場合も_m a
_がありますが、2番目の場合のみ_m a
_が関数_(a->m b)
_を適用するかどうかを決定できます。次に、関数_(a->m b)
_は、次にバインドされた関数を適用するかどうかを「決定」できます-b
を「含まない」_m b
_を生成することにより(_[]
_、Nothing
、Left
など) 。
Applicative
には、関数「内部」m (a->b)
がそのような「決定」を行う方法はありません-それらは常にb
型の値を生成します。
_f 1 = Nothing -- here f "decides" to produce Nothing
f x = Just x
Just 1 >>= f >>= g -- g doesn't get applied, because f decided so.
_
Applicative
ではこれは不可能なので、例を示すことはできません。最も近い:
_f 1 = 0
f x = x
g <$> f <$> Just 1 -- oh well, this will produce Just 0, but can't stop g
-- from getting applied
_
しかし、次の説明は私にはあいまいに見え、私はモナド計算/アクションの「結果」が何を意味するのかを正確に理解できませんでした。
さて、その曖昧さはやや意図的です。なぜなら、「結果」が単項計算の結果であるのは、各タイプに依存するものだからです。最良の答えは少しトートロジーです:「結果」(またはresults、複数ある可能性があるため)は、(>>=) :: Monad m => m a -> (a -> m b) -> m b
のインスタンスの実装が関数を呼び出す任意の値です引数付き。
それで、もしモナドを作る
Maybe
に値を入れると、この「計算」の結果はどうなりますか?
Maybe
モナドは次のようになります。
instance Monad Maybe where
return = Just
Nothing >>= _ = Nothing
Just a >>= k = k a
ここで「結果」として認められるのは、>>=
の2番目の式のa
だけです。これは、>>=
の2番目の引数に「供給」されるのはこれだけだからです。
他の答えはifA
対ifM
の違いについて深く入り込んでいるので、別の重要な違いを強調したいと思いました:applicatives compose、monads don ' t。 Monad
sを使用して、2つの既存の効果を組み合わせたMonad
を作成する場合、それらの1つをモナド変換子として書き換える必要があります。対照的に、2つのApplicatives
がある場合、以下に示すように、それらからより複雑なものを簡単に作成できます。 (コードは transformers
からコピーペーストされます。)
-- | The composition of two functors.
newtype Compose f g a = Compose { getCompose :: f (g a) }
-- | The composition of two functors is also a functor.
instance (Functor f, Functor g) => Functor (Compose f g) where
fmap f (Compose x) = Compose (fmap (fmap f) x)
-- | The composition of two applicatives is also an applicative.
instance (Applicative f, Applicative g) => Applicative (Compose f g) where
pure x = Compose (pure (pure x))
Compose f <*> Compose x = Compose ((<*>) <$> f <*> x)
-- | The product of two functors.
data Product f g a = Pair (f a) (g a)
-- | The product of two functors is also a functor.
instance (Functor f, Functor g) => Functor (Product f g) where
fmap f (Pair x y) = Pair (fmap f x) (fmap f y)
-- | The product of two applicatives is also an applicative.
instance (Applicative f, Applicative g) => Applicative (Product f g) where
pure x = Pair (pure x) (pure x)
Pair f g <*> Pair x y = Pair (f <*> x) (g <*> y)
-- | The sum of a functor @f@ with the 'Identity' functor
data Lift f a = Pure a | Other (f a)
-- | The sum of two functors is always a functor.
instance (Functor f) => Functor (Lift f) where
fmap f (Pure x) = Pure (f x)
fmap f (Other y) = Other (fmap f y)
-- | The sum of any applicative with 'Identity' is also an applicative
instance (Applicative f) => Applicative (Lift f) where
pure = Pure
Pure f <*> Pure x = Pure (f x)
Pure f <*> Other y = Other (f <$> y)
Other f <*> Pure x = Other (($ x) <$> f)
Other f <*> Other y = Other (f <*> y)
ここで、Constant
functor/applicativeを追加すると:
newtype Constant a b = Constant { getConstant :: a }
instance Functor (Constant a) where
fmap f (Constant x) = Constant x
instance (Monoid a) => Applicative (Constant a) where
pure _ = Constant mempty
Constant x <*> Constant y = Constant (x `mappend` y)
... Either
およびLift
以外の他の応答から「applicative Constant
」を組み立てることができます。
type Error e a = Lift (Constant e) a
コンテキスト内のすべてが適用されることを理解しているため、この「iffy miffy」に関する見解を共有したいと思います。たとえば、
iffy :: Applicative f => f Bool -> f a -> f a -> f a
iffy fb ft fe = cond <$> fb <*> ft <*> fe where
cond b t e = if b then t else e
case 1>> iffy (Just True) (Just “True”) Nothing ->> Nothing
uppsはちょうど「True」でなければなりません...
case 2>> iffy (Just False) (Just “True”) (Just "False") ->> Just "False"
(コンテキスト内で「良い」選択が行われます)この方法をこの方法で説明しました。>> 1の場合の計算が終わる直前に、「チェーン」でそのようなものを取得します。
Just (Cond True "True") <*> something [something being "accidentaly" Nothing]
applicativeの定義によると、次のように評価されます。
fmap (Cond True "True") something
これは、「何か」is NothingがFunctor制約に従ってNothingになります(Nothing上のfmapはNothingを提供します)。また、「fmap f Nothing = something」というストーリーの終わりでFunctorを定義することはできません。