web-dev-qa-db-ja.com

HaskellでのMonadとApplicativeの違い

MonadApplicativeの違いについて、 typeclassopedia から以下を読みました。 joinにはApplicativeがないことを理解できます。しかし、次の説明は私にはあいまいに見え、私はモナド計算/アクションの「結果」が何を意味するのかを正確に理解できませんでした。それで、もしモナドを作るMaybeに値を入れると、この「計算」の結果はどうなりますか?

(>> =)のタイプをさらに詳しく見てみましょう。基本的な直感は、2つの計算を1つの大きな計算に結合することです。最初の引数m aは、最初の計算です。ただし、2番目の引数が単なるm bの場合は退屈です。計算が相互に作用する方法はありません(実際、これはまさにApplicativeの状況です)。そのため、(>> =)の2番目の引数のタイプはa-> m bです。このタイプの関数は、最初の計算の結果が与えられると、実行する2番目の計算を生成できます。 ...直感的に、MonadをApplicativeよりも強力にするのは、前の計算の出力を使用して次に実行する計算を決定する機能です。 Applicative計算の構造は固定されていますが、Monad計算の構造は中間結果に基づいて変更できます。

「前の計算からの出力を使用して、次に実行する計算を決定する能力」を示す具体的な例はありますか?

49
tinlyx

私のお気に入りの例は、「純粋に適用可能などちらか」です。いずれかの基本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

ここで、apreturnの前の左から右への順序にすぎません。

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!

左の引数を「過去」、右の引数を「未来」と考えると、(<*>)は未来を「開く」ことが許可されているため、(>>=)と比較して特別であることに注意してください。 「未来」を計算するために「過去」からの結果を必ずしも必要とするのではなく、過去と並行して。

つまり、チェーンにApplicativesが存在する場合はEithersを無視して、純粋にRightLeftを使用してエラーを収集できることを直接意味します。

> 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は、引数計算に埋め込まれた値に応じて結果計算の概念を正確に具体化するため、この不可能性が生じます。

62
J. Abrahamson

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 )

すべての関数は「内部」計算で動作するため、チェーンを分割することはできません。各関数は、渡された値を変換するだけです。計算構造の「形状」は、関数の観点からは完全に「外側」にあります。

関数は失敗を示す特別な値を返すことができますが、計算の次のステップをスキップすることはできません。それらはすべて、特別な方法でも特別な値を処理する必要があります。計算の形状は、受け取った値に応じて変更できません。

モナドでは、関数自体が選択に応じて計算を構築します。

38
Will Ness

これが私の@Jの見解です。 ifAが内部で値を使用できない理由に関するAbrahamsonの例_(pure True)_。本質的には、joinMonadからApplicative関数が存在しないことに変わりはありません。これは、 typeclassopedia で指定された2つの異なる観点を統合して、MonadApplicativeの違いを説明します。

@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!
_

EitherMonadと同様の短絡効果があります)、および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)_の内容は後の計算で使用できません。しかし、技術的に言えば、これは正しくありません。 MonadFunctorを持つfmapでもあるため、canは_(pure True)_のコンテンツを使用できます。我々はできる:

_ifA' b t e = fmap (\x -> if x then t else e) b
_

問題は、f (f a)である_ifA'_の戻り型にあります。 Applicativeでは、ネストされた2つのApplicativeSを1つにまとめる方法はありません。しかし、この折りたたみ関数は、joinMonadが実行するものです。そう、

_ifA = join . ifA' 
_

ifAを適切に実装できれば、joinの方程式を満たします。ここでApplicativeが欠落しているのは、まさにjoin関数です。つまり、Applicativeの前の結果の結果を何らかの方法で使用できます。ただし、Applicativeフレームワークでこれを行うには、戻り値の型をネストされた適用可能な値に拡張する必要があります。これは、単一レベルの適用可能な値に戻す手段がありません。たとえば、ApplicativeSを適切に使用して関数を構成できないため、これは深刻な問題になります。 joinを使用すると問題は解決しますが、joinを導入すると、ApplicativeMonadにプロモートされます。

15
tinlyx

違いの鍵は、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_を生成することにより(_[]_、NothingLeftなど) 。

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
_
13
Sassa NF

しかし、次の説明は私にはあいまいに見え、私はモナド計算/アクションの「結果」が何を意味するのかを正確に理解できませんでした。

さて、その曖昧さはやや意図的です。なぜなら、「結果」が単項計算の結果であるのは、各タイプに依存するものだからです。最良の答えは少しトートロジーです:「結果」(または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番目の引数に「供給」されるのはこれだけだからです。

他の答えはifAifMの違いについて深く入り込んでいるので、別の重要な違いを強調したいと思いました:applicatives compose、monads don ' tMonadsを使用して、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
2
Luis Casillas

コンテキスト内のすべてが適用されることを理解しているため、この「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を定義することはできません。

0
user3680029