75

Typeclassopediaを調べて、型クラスを学習しました。私は理解に行き詰まっていますAlternative(そしてMonadPlus、さらに言えば)。

私が抱えている問題:

  • ペディアによると、「Alternative 型クラスは、モノイド構造も持つ Applicative ファンクター用です。」よくわかりません -- オルタナティブとは、モノイドとはまったく違うものを意味するのではないですか? つまり、Alternative 型クラスのポイントは 2 つのものの間で選択することであると理解していましたが、モノイドはものを組み合わせることであると理解していました。

  • emptyAlternative にメソッド/メンバーが必要なのはなぜですか? 私は間違っているかもしれませんが、まったく使用されていないようです...少なくとも私が見つけたコードでは。そして、それはクラスのテーマに合わないようです.2つのものがあり、1つを選択する必要がある場合、「空」は何のために必要ですか?

  • Alternative 型クラスに Applicative 制約が必要なのはなぜ* -> *ですか? なぜ持っていないの<|> :: a -> a -> aですか?すべてのインスタンスは同じ方法で実装できます...私は(確信が持てません)と思います。モノイドが提供しない価値は何ですか?

  • MonadPlus型クラスのポイントは何ですか? Monad何かを aとの両方として使用するだけで、その良さをすべて解き放つことはできませんAlternativeか? なぜそれを捨てないのですか?(私は間違っていると確信していますが、反例はありません)

願わくば、これらすべての質問が首尾一貫していることを願っています ... !


報奨金の更新: @Antal の回答は素晴らしいスタートですが、Q3 はまだ開いています: Monoid が提供しないオルタナティブは何を提供しますか? この回答は、具体的な例と、Alternative のより高い親切さが Monoid とどのように区別されるかについての具体的な議論がないため、満足のいくものではありません。

Applicative の効果と Monoid の動作を組み合わせる場合は、次のようにしてください。

liftA2 mappend

多くの Monoid インスタンスは Alternative インスタンスとまったく同じであるため、これは私にとってさらに混乱を招きます。

だからこそ、オルタナティブが必要な理由と、それがモノイドとどのように異なるか、または何か異なることを意味するかを示す具体的な例を探しています.

4

5 に答える 5

92

まず、これらの質問のそれぞれに簡単に答えてみましょう。次に、それぞれをより詳細な回答に展開しますが、これらの短い回答がそれらをナビゲートするのに役立つことを願っています.

  1. いいえ、別の意味ではAlternativeありません。は、と の両方の構造を持つ型用です。「選択」と「組み合わせ」は、同じ広い概念に対する 2 つの異なる直感です。MonoidAlternativeApplicativeMonoid

  2. Alternativeこれはempty<|>設計者がこれが便利だと考えたからであり、これがモノイドを生成するからです。ピッキングで言えばempty、ありえない選択をすることに相当します。

  3. 前者は後者よりも多くの法律に従う (またはすべき) ため、Alternative両方が必要です。これらの法則は、型コンストラクターのモノイドおよび適用可能な構造に関連しています。さらに、内側の型に依存することはできませんが、依存することはできます。MonoidAlternativeMonoid

  4. MonadPlusAlternativeより多くの法則に従う必要があるため、よりもわずかに強力です。これらの法則は、適用可能な構造に加えて、モノイド構造をモナド構造に関連付けます。両方のインスタンスがある場合、それらは一致するはずです。


Alternativeとは全然違う意味じゃないMonoidですか?

あまり!あなたの混乱の理由の 1 つは、HaskellMonoidクラスがかなり悪い (一般的ではない) 名前を使用していることです。これは、数学者がモノイドを定義する方法です (それについて非常に明示的です)。

意味。モノイドは、区別された要素 ε ∈ M と二項演算子 · : M × M → M を備えた集合Mあり並置表さ2つの条件が成り立ちます。

  1. εは単位元です。すべての mMに対して、m ε = ε m = mです。
  2. · 連想: すべての m ₁, m ₂, m ₃ ∈ M , ( m ₁<em>m₂) m ₃ = m ₁( m ₂<em>m₃).

それでおしまい。Haskell では、ε は綴りmempty、 · は綴りmappend(最近では<>) であり、集合Mは の型Mですinstance Monoid M where ...

この定義を見ると、「結合」(または「選択」) について何も述べていないことがわかります。· とεについて述べていますが、それだけです。さて、物事を組み合わせることがこの構造でうまく機能することは確かに真実です: εは何も持たないことに対応し、m ₁<em>m₂ は、m ₁ とm ₂ のものを一緒にグロムすると、それらのすべてを含む新しいものを取得できることを示しています。もの。しかし、別の直観があります。εはまったく選択肢がないことに対応し、m 1<em>m2 は m 1 と m 2 の間の選択に対応ます。これが「ピッキング」の直感です。どちらもモノイドの法則に従うことに注意してください。

  1. 何も持たないこと、選択の余地がないこと、それがアイデンティティです。
    • 何も持っていないのに何かと一緒にグロムすると、また同じものになってしまいます。
    • まったく選択肢がない (不可能なこと) か、他の選択肢がある場合、他の (可能な) 選択肢を選ばなければなりません。
  2. コレクションをまとめて選択することは、どちらも連想的です。
    • 3 つのコレクションがある場合、最初の 2 つをまとめてから 3 つ目をグロム化するか、最後の 2 つをまとめてから最初のコレクションをグロム化するかは問題ではありません。いずれにせよ、最終的には同じ総グロム コレクションになります。
    • 3 つの選択肢がある場合、(a) 最初に 1 番目または 2 番目と 3 番目のいずれかを選択し、必要に応じて 1 番目と 2 番目のいずれかを選択するか、(b) 最初に 1 番目から選択するかは問題ではありません2 番目または 3 番目、そして必要に応じて 2 番目と 3 番目の間。いずれにせよ、私は好きなものを選ぶことができます。

(注: 私はここで速くてゆるく遊んでいます。それが直感である理由です。たとえば、次のことを覚えておくことが重要です。 <em>m₁.)

見よ: これらの種類の両方 (および他の多くの数 — 乗算は、実際には「結合」または「選択」のどちらかですか?) は同じ規則に従います。直感を持つことは理解を深めるために重要ですが、実際に何が起こっているかを決定するのはルールと定義です。

そして最良の部分は、これらの両方の直感が同じキャリアによって解釈できることです! Mを空集合を含む集合の集合 (すべての集合の集合ではない!)とし、 εを空集合 ∅ とし、 · 集合和∪ とする。∅ が ∪ の単位元であり、∪ が結合的であることは簡単にわかります。したがって、( M ,∅,∪) はモノイドであると結論付けることができます。今:

  1. 集合を物事の集まりと考えると、∪ はそれらをまとめてより多くのものを得る、つまり「組み合わせる」直観に対応します。
  2. セットを可能なアクションを表すものと考えると、∪ は選択可能なアクションのプールを増やすことに対応します。つまり、「選択」の直感です。

これはまさに[]Haskell で行われていることです:[a]Monoidfor allaであり[]、アプリケーション ファンクター (およびモナド) として非決定性を表すために使用されます。結合とピッキングの両方の直観は、同じタイプで一致します:mempty = empty = []mappend = (<|>) = (++).

したがって、Alternativeクラスは、(a) アプリカティブ ファンクターであり、(b) 型でインスタンス化されると、いくつかの規則に従う値とバイナリ関数を持つオブジェクトを表すためだけに存在します。どのルール?モノイド規則。なんで?それは有用であることが判明したため:-)


Alternative空のメソッド/メンバーが必要なのはなぜですか?

ええと、皮肉な答えは「Alternativeモノイド構造を表しているからです」です。しかし、本当の問題は、なぜモノイド構造なのかということです。なぜ半群、 εのないモノイドではないのでしょうか? 答えの 1 つは、モノイドの方が便利だと主張することです。多くの人 (おそらくEdward Kmettではない) がこれに同意すると思います。ほとんどの場合、適切な(<|>)/ / · があれば、適切な// εmappendを定義できます。一方で、より多くのものを傘の下に置くことができるので、余分な一般性を持つことは良いことです.emptymempty

また、これが「ピッキング」の直感とどのように噛み合っているかを知りたいと考えています。ある意味では、「『ピッキング』の直感をいつ捨てるかを知ること」が正解であることを念頭に置いて、この 2 つを統合することができると思います。非決定論の適用的関手 を考えてみましょう[]。type の 2 つの値を と組み合わせる[a](<|>)、左からのアクションまたは右からのアクションのいずれかを非決定論的に選択することに対応します。しかし、場合によっては、一方の側で可能なアクションがまったくない場合もありますが、それは問題ありません。同様に、パーサーについて考えると、(<|>)左側または右側にあるものを解析するパーサーを表します (「選択」)。そして、常に失敗するパーサーがある場合、それは ID になります。それを選択すると、すぐにその選択を拒否し、別のパーサーを試します。

とは言っても、ほぼ に似ているが を欠いているクラスを持つことは完全に可能であること覚えておいてください。それは完全に有効であり、のスーパークラスである可能性もありますが、たまたま Haskell が行ったことではありません。おそらく、これは何が役立つかについての推測ではありません。AlternativeemptyAlternative


Alternative型クラスにApplicative制約が必要なのはなぜですか* -> *? … [使用] だけではないのはなぜliftA2 mappendですか?

Applicativeさて、これら 3 つの提案された変更のそれぞれについて考えてみましょうAlternativeAlternativeの引数の種類を変更します。liftA2 mappendの代わりに<|>とのpure mempty代わりに使用しemptyます。この 3 番目の変更が最も異なるため、最初に見ていきます。完全に削除しAlternativeて、クラスを 2 つの単純な関数に置き換えたとします。

fempty :: (Applicative f, Monoid a) => f a
fempty = pure mempty

(>|<) :: (Applicative f, Monoid a) => f a -> f a -> f a
(>|<) = liftA2 mappend

someとの定義を保持することさえできmanyます。そして、これ確かにモノイド構造を与えます。それは本当です。しかし、それは私たちに間違ったものを与えているようです. のインスタンスではないため、Just fst >|< Just snd失敗するはずです。いいえ、しかし、それは上記のコードの結果になります。私たちが望むモノイドインスタンスは、内部型にとらわれないものです (非常に関連する haskell-cafe の議論Matthew Farkas-Dyckから用語を借りて、いくつかの非常によく似た質問をします)。構造は、の引数の構造ではなく、 の構造によって決定されるモノイドに関するものです。(a,a) -> aMonoidAlternativeff

Alternativeある種の型クラスとして残したいと考えているので、それを変更するための 2 つの提案された方法を見てみましょう。種類を変更する場合は、制約を取り除く必要があります。親切なことしか話さないので、それを参照する方法はありません。これにより、2 つの変更が可能になります。最初の、よりマイナーな変更は、制約を取り除き、種類をそのままにしておくことです。ApplicativeApplicative* -> *Applicative

class Alternative' f where
  empty' :: f a
  (<||>) :: f a -> f a -> f a

もう 1 つの大きな変更は、制約を取り除きApplicative、種類を変更することです。

class Alternative'' a where
  empty'' :: a
  (<|||>) :: a -> a -> a

someどちらの場合も/を取り除く必要がありますmanyが、それで問題ありません。(Applicative f, Alternative' f) => f a -> f [a]タイプorを持つスタンドアロン関数としてそれらを定義できます(Applicative f, Alternative'' (f [a])) => f a -> f [a]

ここで、型変数の種類を変更する 2 番目のケースでは、クラスが とまったく同じであることがわかります(または、それでも,Monoidを削除したい場合)。したがって、別のクラスを使用する利点はありません。そして実際、kind 変数をそのままにして制約を取り除いても、ただ になりますが、これらの量化された制約を Haskell で書くことはできません。(これは、上記の内部型不可知論を表現していることに注意してください。) したがって、これらの変更のいずれかを行うことができれば、保持する理由はありません(その定量化された制約を表現できることを除いて、それはほとんど説得力があるとは思えません)。 .empty''SemigroupApplicativeAlternativeforall a. Monoid (f a)Alternative

つまり、問題は「パーツと、両方のインスタンスである のパーツとの間に関係があるか」ということになりAlternativeます。そして、ドキュメントには何もありませんが、私は立場に立ってイエスと言うつもりです— または、少なくとも、ドキュメントがあるべきです. (モノイドの法則に加えて) に関連するいくつかの法則に従うことになっていると思います。特に、それらの法律は次のようなものだと思いますApplicativefAlternativeApplicative

  1. 右分配 (の<*>):  (f <|> g) <*> a = (f <*> a) <|> (g <*> a)
  2. 右吸収 ( の場合<*>):  empty <*> a = empty
  3. 左分配係数 (のfmap):  f <$> (a <|> b) = (f <$> a) <|> (f <$> b)
  4. 左吸収 ( の場合fmap):  f <$> empty = empty

[]これらの法則はと、 および (そのインスタンスがインスタンスであるMaybeふりをする)に当てはまるように見えますが、証明や徹底的なテストは行っていません。(例えば、私は当初、分配性は に対して保持される考えていたが、これは に対して間違った順序で「効果を実行する」。) しかし類推すると、 が同様の法則に従うことが期待されるのは事実である (明らかにいくつかの法則があるが、どちらについてのあいまいさ)。私はもともと第三法則を主張したかったのですが、それは当然のことのようです。MonadPlusAlternativeIO<*>[]MonadPlus

  • 左吸収 ( の場合<*>):  a <*> empty = empty

しかし、私はこの法則を信じ[]て従っていますが、そうではありません。(次の段落で明らかになる理由により) それを要求しないのが最善だと思います。MaybeIO

実際、Edward Kmettも同様の見解を支持するスライドをいくつか持っているようです。それに入るには、もう少し数学的な専門用語を含む簡単な余談をする必要があります。最後のスライド「I Want More Structure」では、「Monoid は Applicative に対すAlternative の引数を取り除くと、RightSemiNearRing が得られます。」</p>

正しいセミナー?「正しいセミナーはどのようにしてそこに入ったのですか?」私はあなたが泣くのを聞きます。 良い、

意味。右ニアセミリング ( これも右セミニアリングですが、Google では前者の方がよく使われているようです) は四重項 ( R ,+,·,0) であり、ここで ( R ,+,0) はモノイド ( R ,·)です。は半群であり、次の 2 つの条件が成り立ちます。

  1. · すべてのr , s , tR , ( s + t ) r = sr + trに対して、 + の上で右分配です。
  2. 0 は · に対して右吸収です: すべてのrRに対して、0 r = 0。

左近セミリングも同様に定義されます。

は真の連想演算子でも二項演算子でもないため<*>、型が一致しません。これは、エドワード・クメットが「議論を捨てる」ことについて話しているときに得ていることだと思います。f a別のオプションは、実際には ( , <|>, <*>, ) が半リンゴイドに近い右側emptyを形成することを望んでいる (これが正しいかどうかはわかりません) と言うかもしれません。要素の特定のペア (グループイド風)。また、( , , , ) は左に近い半りんご形であるとも言いたいのですが、これはおそらくf a<|><$>emptyApplicative法則と右半りんご形に近い構造。しかし今、私は頭を悩ませています。とにかく、これは深く関係していません.

いずれにせよ、これらの法則はモノイドの法則よりも強力Monoidであるため、完全に有効なインスタンスが無効なインスタンスになることを意味しAlternativeます。標準ライブラリには、(少なくとも) 2 つの例があります:Monoid a => (a,)Maybe. それぞれを簡単に見てみましょう。

任意の 2 つのモノイドが与えられると、それらの積はモノイドになります。Monoidその結果、タプルは明らかな方法でのインスタンスにすることができます(基本パッケージの source を再フォーマットします):

instance (Monoid a, Monoid b) => Monoid (a,b) where
  mempty = (mempty, mempty)
  (a1,b1) `mappend` (a2,b2) = (a1 `mappend` a2, b1 `mappend` b2)

同様に、最初のコンポーネントがモノイドの要素であるタプルをApplicative、モノイド要素を蓄積することにより (基本パッケージの source を再フォーマットして) のインスタンスにすることができます。

instance Monoid a => Applicative ((,) a) where
  pure x = (mempty, x)
  (u, f) <*> (v, x) = (u `mappend` v, f x)

ただし、タプルは のインスタンスではありませんAlternative。なぜなら、Monoid a => (a,b)すべての型に対してモノイド構造が存在するわけではなくbAlternativeのモノイド構造は内部型不可知論的でなければならないからです。bを表現できるようにするためには、モナドでなければならないだけでなく、関数(f <> g) <*> aのインスタンスを使用する必要がありMonoidます。これは、形式の関数ですMonoid b => a -> b。そして、必要なモノイド構造がすべて揃っている場合でも、 4 つの法則すべてに違反しています。Alternativeこれを見るには、 letssf n = (Sum n, (<> Sum n))と let ssn = (Sum n, Sum n). 次に、 を書く(<>)mappend、次の結果が得られます (GHCi で時折型注釈を付けて確認できます)。

  1. 右分配率:
    • (ssf 1 <> ssf 1) <*> ssn 1 = (Sum 3, Sum 4)
    • (ssf 1 <*> ssn 1) <> (ssf 1 <*> ssn 1) = (Sum 4, Sum 4)
  2. 右吸収:
    • mempty <*> ssn 1 = (Sum 1, Sum 0)
    • mempty = (Sum 0, Sum 0)
  3. 左分配率:
    • (<> Sum 1) <$> (ssn 1 <> ssn 1) = (Sum 2, Sum 3)
    • ((<> Sum 1) <$> ssn 1) <> ((<> Sum 1) <$> ssn 1) = (Sum 2, Sum 4)
  4. 左吸収:
    • (<> Sum 1) <$> mempty = (Sum 0, Sum 1)
    • mempty = (Sum 1, Sum 1)

次に、 を考えMaybeます。Maybe現状では、MonoidAlternativeインスタンスは一致しません。(このセクションの冒頭で言及した haskell-cafe の議論では、これを変更することが提案されてますが、同じ効果をもたらすsemigroups パッケージOptionnewtype があります。 ) 基本パッケージには半群クラスがないため、モノイドを持ち上げるだけなので、次のようになります (基本パッケージの sourceを再フォーマットします):MonoidMaybeNothing

instance Monoid a => Monoid (Maybe a) where
  mempty = Nothing
  Nothing `mappend` m       = m
  m       `mappend` Nothing = m
  Just m1 `mappend` Just m2 = Just (m1 `mappend` m2)

一方、 は、Alternative失敗Maybeした場合の優先順位の高い選択を表すため、次のようになります (再び基本パッケージのソースを再フォーマットします)。

instance Alternative Maybe where
  empty = Nothing
  Nothing <|> r = r
  l       <|> _ = l

そして、後者だけがAlternative法律を満たしていることがわかりました。インスタンスのMonoid失敗は のものほどひどくありません(,)。ほとんど偶然ですが、これはに関する法則に従います。これは、 for 関数の唯一のインスタンスの動作から生じます。これは、(前述のように) モノイドをリーダー アプリカティブ ファンクターに戻す関数を持ち上げます。解決すれば (すべて非常に機械的です)、すべての右分配性と右吸収がとインスタンスの両方で保持され、 の左吸収も同様であることがわかります。また、次のように、インスタンスの左分配性が保持されます。<*>Monoid<*>AlternativeMonoidfmapfmapAlternative

f <$> (Nothing <|> b)
  = f <$> b                          by the definition of (<|>)
  = Nothing <|> (f <$> b)            by the definition of (<|>)
  = (f <$> Nothing) <|> (f <$> b)    by the definition of (<$>)

f <$> (Just a <|> b)
  = f <$> Just a                     by the definition of (<|>)
  = Just (f a)                       by the definition of (<$>)
  = Just (f a) <|> (f <$> b)         by the definition of (<|>)
  = (f <$> Just a) <|> (f <$> b)     by the definition of (<$>)

ただし、Monoidインスタンスでは失敗します。のために書い(<>)mappend、私たちは持っています:

  • (<> Sum 1) <$> (Just (Sum 0) <> Just (Sum 0)) = Just (Sum 1)
  • ((<> Sum 1) <$> Just (Sum 0)) <> ((<> Sum 1) <$> Just (Sum 0)) = Just (Sum 2)

さて、この例には注意点が 1 つあります。との互換性のみが必要で、 とAlternativeの互換性が必要ない場合は問題<*>ありません。上記の Edward Kmett のスライドは に言及していませんが、それに関しても法律を要求するのは合理的だと思います。それにもかかわらず、私はこれを裏付けるものを見つけることができません。<$>Maybe<$>

Alternativeしたがって、 であることは であることよりも強い要件Monoidであり、別のクラスが必要であると結論付けることができます。これの最も純粋な例は、内部型に依存しないMonoidインスタンスを持つ型と、Applicative互いに互換性のないインスタンスです。ただし、基本パッケージにはそのようなタイプはなく、思いつきません。(驚いたことに、何も存在しない可能性もあります。) それでも、これらの内部型グノーシスの例は、2 つの型クラスが異なる必要がある理由を示しています。


MonadPlus型クラスのポイントは何ですか?

MonadPlus、 のようAlternativeに、 の強化ですが、 の代わりにMonoidに関して。Edward Kmettによると、質問「型クラス、、、および?」の違い</a> に対する回答で、も より強力です。たとえば、法則は を暗示していませんAndrewCは、これについて 2 つの例を示しています。には2 つの潜在的な法則セットがあるという事実によって、問題は複雑になります。がとでモノイドを形成することになっていることは普遍的に合意されており、左ゼロの法則を満たすことになっています。MonadApplicativeMonadPlusAlternativeMonoidMonadPlusAlternativeempty <*> aempty >>= fMaybeMonadPlusMonadPlusmplusmemptymempty >>= f = mempty. ただし、一部のMonadPlusses は左分布, mplus a b >>= f = mplus (a >>= f) (b >>= f);を満たします。およびその他は、左キャッチ、を満たしmplus (return a) b = return aます。( の左ゼロ/分布は のMonadPlus右分布/吸収に類似していることに注意してくださいAlternative。 ;(<*>)は よりも類似して(=<<)(>>=)ます。) 左分布はおそらく「より良い」ためMonadPlus、 などの左キャッチを満たすすべてのインスタンスは第 1 種MaybeではありAlternativeませんが、第 1 種ではありません。のMonadPlus。また、左キャッチは順序付けに依存しているため、バイアスではなくバイアスのインスタンスをMaybe持つnewtype ラッパーを想像できます。Alternativea <|> Just b = Just b. これは、左分布も左キャッチも満たさないでしょうが、完全に有効なAlternative.

ただし、あるタイプはそのMonadPlusインスタンスとそのインスタンスが一致する必要があるため (これは、 s であるsに対して等しいAlternativeことが必要であるのと同じように必要であると私は信じていapます)、代わりにクラスを次のように定義することを想像できます。(<*>)MonadApplicativeMonadPlus

class (Monad m, Alternative m) => MonadPlus' m

クラスは新しい関数を宣言する必要はありません。emptyこれは、指定された型が従う法則についての単なる約束(<|>)です。この設計手法は、Haskell 標準ライブラリでは使用されていませんが、同様の目的で、より数学的なパッケージで使用されています。たとえば、格子パッケージは、格子が吸収法則によってリンクされている同じタイプの結合半格子と会合半格子であるという考えを表現するためにそれを使用します。

常に一致することをAlternative保証したい場合でも、に対して同じことを行うことができない理由は、種類の不一致のためです。目的のクラス宣言の形式は次のとおりです。AlternativeMonoid

class (Applicative f, forall a. Monoid (f a)) => Alternative''' f

しかし (上で述べたように) GHC Haskell でさえ、定量化された制約をサポートしていません。

また、AlternativeのスーパークラスにMonadPlusなるには のスーパークラスである必要Applicativeがあることに注意してくださいMonad。その問題に遭遇した場合、明らかな方法でanyを にWrappedMonad変える newtypeが常に存在します。あなたが期待することを正確に行う があります。MonadApplicativeinstance MonadPlus m => Alternative (WrappedMonad m) where ...

于 2012-10-26T06:03:23.973 に答える
20
import Data.Monoid
import Control.Applicative

Maybeモノイドとオルタナティブがファンクターとファンクターとどのように相互作用するかの例をたどってみましょうZipList。ただし、一部はすべての定義を頭の中で新鮮にするため、一部はタブをハックのビットに常に切り替えるのをやめるために、ゼロから始めましょう。主に、この過去の ghciを実行してタイプミスを修正できるようにするためです。

(<>) :: Monoid a => a -> a -> a
(<>) = mappend -- I'll be using <> freely instead of `mappend`.

多分クローンは次のとおりです。

data Perhaps a = Yes a | No  deriving (Eq, Show)

instance Functor Perhaps where
   fmap f (Yes a) = Yes (f a)
   fmap f No      = No

instance Applicative Perhaps where
   pure a = Yes a
   No    <*> _     = No
   _     <*> No    = No
   Yes f <*> Yes x = Yes (f x)
   

そして今ZipList:

data Zip a = Zip [a]  deriving (Eq,Show)

instance Functor Zip where
   fmap f (Zip xs) = Zip (map f xs)

instance Applicative Zip where
   Zip fs <*> Zip xs = Zip (zipWith id fs xs)   -- zip them up, applying the fs to the xs
   pure a = Zip (repeat a)   -- infinite so that when you zip with something, lengths don't change

構造 1: 要素の結合: モノイド

多分クローン

まず、見てみましょうPerhaps String。それらを組み合わせるには2つの方法があります。まず連結

(<++>) :: Perhaps String -> Perhaps String -> Perhaps String
Yes xs <++> Yes ys = Yes (xs ++ ys)
Yes xs <++> No     = Yes xs
No     <++> Yes ys = Yes ys
No     <++> No     = No

連結は、NoあたかもYes []. に等しいliftA2 (++)です。++それは賢明で便利ですが、単に使用することから、任意の結合方法を使用することへと一般化できるかもしれません。

(<++>) :: Monoid a => Perhaps a -> Perhaps a -> Perhaps a
Yes xs <++> Yes ys = Yes (xs `mappend` ys)
Yes xs <++> No     = Yes xs
No     <++> Yes ys = Yes ys
No     <++> No     = No

のこのモノイド構造はPerhaps、レベルで可能な限り機能しようとしaます。Monoid a制約に注意してください。これは、aレベルから構造を使用していることを示しています。これは代替構造ではなく、派生 (持ち上げられた) モノイド構造です。

instance Monoid a => Monoid (Perhaps a) where
   mappend = (<++>)
   mempty = No

ここでは、データ a の構造を使用して、全体に構造を追加しました。sを組み合わせていれば、代わりにコンテキストSetを追加できます。Ord a

ZipList クローン

では、どのように要素を zipList と組み合わせる必要があるでしょうか? それらを結合する場合、これらを何に圧縮する必要がありますか?

   Zip ["HELLO","MUM","HOW","ARE","YOU?"] 
<> Zip ["this", "is", "fun"]
=  Zip ["HELLO" ? "this",   "MUM" ? "is",   "HOW" ? "fun"]

mempty = ["","","","",..]   -- sensible zero element for zipping with ?

しかし、何に使用する必要がありますか?。ここでの唯一の賢明な選択は++. 実際、リストの場合、(<>) = (++)

   Zip [Just 1,  Nothing, Just 3, Just 4]
<> Zip [Just 40, Just 70, Nothing]
 =  Zip [Just 1 ? Just 40,    Nothing ? Just 70,    Just 3 ? Nothing]

mempty = [Nothing, Nothing, Nothing, .....]  -- sensible zero element

しかし、?要素を結合することを意図していると言うので、Monoid の要素結合演算子を再び使用する必要があります<>

instance Monoid a => Monoid (Zip a) where
   Zip as `mappend` Zip bs = Zip (zipWith (<>) as bs) -- zipWith the internal mappend
   mempty = Zip (repeat mempty)  -- repeat the internal mempty

これは、zip を使用して要素を結合する唯一の賢明な方法です。つまり、これが唯一の賢明なモノイド インスタンスです。

Int興味深いことに、Haskell はsを組み合わせる方法を知らないため、上記の Maybe の例ではうまくいきませ+*。数値データのモノイド インスタンスを取得するには、それらをラップするSumProduct、使用するモノイドを指定します。

Zip [Just (Sum 1),   Nothing,       Just (Sum 3), Just (Sum 4)] <> 
Zip [Just (Sum 40),  Just (Sum 70), Nothing]
= Zip [Just (Sum 41),Just (Sum 70), Just (Sum 3)]

   Zip [Product 5,Product 10,Product 15] 
<> Zip [Product 3, Product 4]
 =  Zip [Product 15,Product 40]

キーポイント

Monoid の型が kind を持っているという事実が、*まさにMonoid aここにコンテキストを配置できることに注意Eq aしてください。 orを追加することもできOrd aます。モノイドでは、生の要素が重要です。Monoid インスタンスは、構造内のデータを操作および結合できるように設計されています。

構造 2: 上位の選択: 代替案

選択演算子は似ていますが、異なります。

多分クローン

(<||>) :: Perhaps String -> Perhaps String -> Perhaps String
Yes xs <||> Yes ys = Yes xs   -- if we can have both, choose the left one
Yes xs <||> No     = Yes xs
No     <||> Yes ys = Yes ys
No     <||> No     = No  

ここでは連結はありません - まったく使用++しませんでした - この組み合わせは純粋にPerhapsレベルで機能するので、型シグネチャを次のように変更しましょう

(<||>) :: Perhaps a -> Perhaps a -> Perhaps a
Yes xs <||> Yes ys = Yes xs   -- if we can have both, choose the left one
Yes xs <||> No     = Yes xs
No     <||> Yes ys = Yes ys
No     <||> No     = No  

制約がないことに注意してください。レベルの構造を使用しているのではなく、aレベルの構造だけを使用していPerhapsます。これは代替構造です。

instance Alternative Perhaps where
   (<|>) = (<||>)
   empty = No  

ZipList クローン

2 つのジップリストからどのように選択すればよいでしょうか?

Zip [1,3,4] <|> Zip [10,20,30,40] = ????

要素で使用するのは非常に魅力的<|>ですが、要素の型が利用できないため使用できません。から始めましょうempty。Alternative を定義するときに要素の型がわからないため、要素を使用できませんZip []。の左 (できれば右) の恒等式である必要があるため<|>

Zip [] <|> Zip ys = Zip ys
Zip xs <|> Zip [] = Zip xs

には 2 つの賢明な選択がありますZip [1,3,4] <|> Zip [10,20,30,40]

  1. Zip [1,3,4]それは最初だから - 多分と一致する
  2. Zip [10,20,30,40]最長であるため -Zip []破棄されることと一致します

pure x = Zip (repeat x)どちらのリストも無限である可能性があるため、長さの比較は決して終了しない可能性があるため、最初のリストを選択する必要があります。したがって、唯一の賢明な代替インスタンスは次のとおりです。

instance Alternative Zip where
   empty = Zip []
   Zip [] <|> x = x
   Zip xs <|> _ = Zip xs

これは、私たちが定義できた唯一の賢明な代替案です。モノイド インスタンスとの違いに注意してください。要素をいじることができず、見ることさえできなかったからです。

キーポイント

Alternativeは種類のコンストラクタを取るため、 orまたはコンテキストを追加する方法* -> *ないことに注意してください。Alternative は、構造内のデータに関する情報を使用することを許可されていません。どれだけやりたくても、破棄する以外に、データに対して何もすることはできません。Ord aEq aMonoid a

キーポイント: オルタナティブとモノイドの違いは何ですか?

多くはありません - どちらもモノイドですが、最後の 2 つのセクションを要約すると:

Monoid *インスタンスを使用すると、内部データを組み合わせることができます。Alternative (* -> *)インスタンスはそれを不可能にします。Monoid は柔軟性を提供し、Alternative は保証を提供します。種類*(* -> *)は、この違いの主な要因です。両方を使用すると、両方の種類の操作を使用できます。

これは正しいことであり、私たちの 2 つのフレーバーはどちらも適切です。の Monoid インスタンスはPerhaps Stringすべての文字をまとめることを表し、Alternative インスタンスは文字列の選択を表します。

Maybe の Monoid インスタンスには何の問題もありません。データを組み合わせて仕事をしています。
Maybe の Alternative インスタンスには何の問題もありません。物事を選択して、その仕事をしています。

Zip の Monoid インスタンスは、その要素を結合します。Zip の代替インスタンスは、リストの 1 つ (最初の空でないリスト) を選択するように強制されます。

両方できると良いですね。

Applicative コンテキストは何に使用されますか?

選択と適用の間には、いくつかの相互作用があります。ここで、彼の質問または回答の途中で述べられている Antal SZ の法則を参照してください。

実用的な観点からは、Alternative は一部の Applicative Functor が選択するために使用されるものであるため、便利です。この機能は Applicatives に使用されていたため、一般的なインターフェイス クラスが発明されました。Applicative Functor は、値を生成する計算 (IO、パーサー、入力 UI 要素など) を表すのに適しています。そのうちのいくつかは失敗を処理する必要があります。代替手段が必要です。

オルタナティブに があるのはなぜemptyですか?

代替が空のメソッド/メンバーを必要とするのはなぜですか? 私は間違っているかもしれませんが、まったく使用されていないようです...少なくとも私が見つけたコードでは。そして、それはクラスのテーマに合わないようです.2つのものがあり、1つを選択する必要がある場合、「空」は何のために必要ですか?

それは、加算に 0 が必要な理由を尋ねるようなものです。何かを加算したい場合、何も加算しないものを用意する意味はありますか? []答えは、1 が掛け算に不可欠であり、リストに不可欠でy=e^xある (そして微積分にとっても重要である) のと同様に、0 はすべてがさらに展開する重要な重要な数であるということです。実際には、これらの何もしない要素を使用して建物を開始します。

sum = foldr (+) 0
concat = foldr (++) []
msum = foldr (`mappend`) mempty          -- any Monoid
whichEverWorksFirst = foldr (<|>) empty  -- any Alternative

MonadPlus を Monad+Alternative に置き換えることはできませんか?

MonadPlus 型クラスのポイントは何ですか? 何かをモナドと代替の両方として使用するだけで、その良さをすべて解き放つことはできませんか? なぜそれを捨てないのですか?(私は間違っていると確信していますが、反例はありません)

あなたは間違っていません。反例はありません。

あなたの興味深い質問に対して、Antal SZ、Petr Pudlák と私は、MonadPlus と Applicative の実際の関係について掘り下げました。答えは、 ここここでMonadPlus(左のディストリビューションの意味で - 詳細についてはリンクをたどってください) も ですAlternativeが、その逆ではありません。

これは、Monad と MonadPlus のインスタンスを作成すると、とにかく Applicative と Alternative の条件を満たすことを意味します。これは、MonadPlus のルール (左の距離) に従う場合、Monad を Applicative にして、Alternative を使用することもできることを意味します。

ただし、MonadPlus クラスを削除すると、ルールを文書化するための賢明な場所が削除され、MonadPlus でなくても何かの Alternative を指定する機能が失われます (技術的には、Maybe に対して行うべきでした)。これらは理論的な理由です。実際的な理由は、既存のコードが壊れるからです。(これは、Applicative も Functor も Monad のスーパークラスではない理由でもあります。)

オルタナティブとモノイドは同じじゃないの?オルタナティヴとモノイドは全然違うじゃないですか。

ペディアによると、「Alternative 型クラスは、モノイド構造も持つ Applicative ファンクター用です。」よくわかりません -- オルタナティブとは、モノイドとはまったく違うものを意味するのではないですか? つまり、Alternative 型クラスのポイントは 2 つのものの間で選択することであると理解していましたが、モノイドはものを組み合わせることであると理解していました。

モノイドとオルタナティブは、賢明な方法で 2 つのオブジェクトから 1 つのオブジェクトを取得する 2 つの方法です。数学は、ユーザーがデータを選択、結合、混合、または爆破するかどうかを気にしません。これが、Alternative が Applicative のモノイドと呼ばれた理由です。あなたは今、そのコンセプトに慣れているように見えますが、今はこう言います

Alternative と Monoid の両方のインスタンスを持つ型の場合、インスタンスは同じであることを意図しています

私はこれに同意しません。私の Maybe と ZipList の例は、なぜ異なるのかについて注意深く説明されていると思います。どちらかといえば、同じものは珍しいと思います。これが適切な例は、単純なリストの 1 つだけです。これは、++リストは を使用したモノイドの基本的な例ですが、一部のコンテキストではリストが要素の不確定な選択として使用されるため、 も使用する<|>必要があるため++です。

于 2012-11-01T10:03:20.163 に答える
8

概要

  • 低レベルのモノイドを持ち上げるだけでなく、アプリカティブ ファンクター レベルで真に結合する、いくつかのアプリカティブ ファンクターのモノイド インスタンス (と同じ操作を提供するインスタンス) を定義する必要があります。以下のエラーの例は、一般に;として定義できないことをlitvar = liftA2 mappend literal variable示しています。この場合、データではなくパーサーを組み合わせることで機能します。<|>liftA2 mappend<|>

  • Monoid を直接使用した場合、インスタンスを定義するために言語拡張が必要になります。Alternativeは高次であるため、言語拡張を必要とせずにこれらのインスタンスを作成できます。

例: パーサー

いくつかの宣言を解析していると想像してみましょう。必要なものをすべてインポートします。

import Text.Parsec
import Text.Parsec.String
import Control.Applicative ((<$>),(<*>),liftA2,empty)
import Data.Monoid
import Data.Char

型を解析する方法について考えます。単純なものを選択します。

data Type = Literal String | Variable String  deriving Show
examples = [Literal "Int",Variable "a"]

それでは、リテラル型のパーサーを書きましょう。

literal :: Parser Type
literal = fmap Literal $ (:) <$> upper <*> many alphaNum

意味:大文字とupper小文字を解析し、次にmany alphaNumeric 文字を解析し、結果を純粋な関数で単一の文字列に結合します(:)。その後、純粋関数Literalを適用してそれらStringの を に変換しTypeます。lower大文字と小文字で始まることを除いて、変数の型をまったく同じ方法で解析します。

variable :: Parser Type
variable = fmap Variable $ (:) <$> lower <*> many alphaNum

それは素晴らしいparseTest literal "Bool" == Literal "Bool"ことです。まさに私たちが望んでいたとおりです。

質問 3a: アプリカティブの効果をモノイドの動作と組み合わせる場合、なぜliftA2 mappend

編集:おっと-実際に使用するのを忘れていました<|>
次に、Alternative を使用してこれら 2 つのパーサーを組み合わせましょう。

types :: Parser Type
types = literal <|> variable

parseTest types "Int" == Literal "Bool"これは、任意の Type:およびを解析できますparseTest types "a" == Variable "a"これは、2 つの値ではなく、2つのパーサーを結合します。これは、データ レベルではなく Applicative Functor レベルで機能するという意味です。

ただし、試してみると:

litvar = liftA2 mappend literal variable

これは、生成した 2 つの値をデータ レベルで結合するようコンパイラに要求することになります。我々が得る

No instance for (Monoid Type)
  arising from a use of `mappend'
Possible fix: add an instance declaration for (Monoid Type)
In the first argument of `liftA2', namely `mappend'
In the expression: liftA2 mappend literal variable
In an equation for `litvar':
    litvar = liftA2 mappend literal variable

それで、最初にわかったことがあります。Alternative クラスはliftA2 mappend、異なるレベルでオブジェクトを結合するため、 とはまったく異なることを行います。つまり、解析されたデータではなく、パーサーを結合します。このように考えるのが好きなら、それは単なるリフトではなく、真に高次のレベルでの組み合わせです。Parser Type私はそのように言うのは好きではあり*ませParserType

(Monoid インスタンスを持つ型の場合liftA2 mappendでも、 と同じパーサーは得られません<|>。試してみると、どちらが次々に解析してから連結するParser Stringかを取得できます。失敗しました。)liftA2 mappend<|>

質問 3b: オルタナティブはモノイドとどのように<|> :: f a -> f a -> f a異なりますmappend :: b -> b -> bか?

まず、Monoid インスタンスに新しい機能を提供しないことに注意してください。

mappend第二に、しかしながら、Monoid を直接使用することには問題があります:と同じ構造であることを示すと同時に、パーサーで使用してみましょうAlternative:

instance Monoid (Parser a) where
    mempty = empty
    mappend = (<|>)

おっとっと!我々が得る

Illegal instance declaration for `Monoid (Parser a)'
  (All instance types must be of the form (T t1 ... tn)
   where T is not a synonym.
   Use -XTypeSynonymInstances if you want to disable this.)
In the instance declaration for `Monoid (Parser a)'

したがって、Applicative functorがある場合fAlternativeインスタンスはそれがモノイドであることを示しますが、それは言語拡張を伴うf aとしてしか宣言できません。Monoid

ファイルの先頭に追加{-# LANGUAGE TypeSynonymInstances #-}したら、問題なく定義できます

typeParser = literal `mappend` variable

そして嬉しいことに、それは機能parseTest typeParser "Yes" == Literal "Yes"parseTest typeParser "a" == Literal "a"ます。

シノニムがなくても (ParserStringシノニムなのでアウトです)、次{-# LANGUAGE FlexibleInstances #-}のようなインスタンスを定義する必要があります。

data MyMaybe a = MyJust a | MyNothing deriving Show
instance Monoid (MyMaybe Int) where
   mempty = MyNothing
   mappend MyNothing x = x
   mappend x MyNothing = x
   mappend (MyJust a) (MyJust b) = MyJust (a + b)

(Maybe のモノイド インスタンスは、基になるモノイドを持ち上げることでこれを回避します。)

標準ライブラリを言語拡張に不必要に依存させることは、明らかに望ましくありません。


それで、あなたはそれを持っています。オルタナティブは、Applicative Functor の単なるモノイドです (モノイドのリフトではありません)。f a -> f a -> f a言語拡張なしで定義できるように、より高次の型が必要です。

完全を期すために、他の質問:

  1. Alternative が空のメソッド/メンバーを必要とするのはなぜですか?
    操作の ID があると便利な場合があるためです。たとえば、anyA = foldr (<|>) empty面倒なエッジ ケースを使用せずに定義できます。

  2. MonadPlus 型クラスのポイントは何ですか? 何かをモナドと代替の両方として使用するだけで、その良さをすべて解き放つことはできませんか? いいえ。あなたがリンクした質問を参照してください:

さらに、たとえ Applicative が Monad のスーパークラスだったとしても、とにかく MonadPlus クラスが必要になるでしょempty <*> m = emptyempty >>= f = empty

....そして、私は例を思いつきました: たぶん。 アンタルの質問に対するこの回答の証拠とともに、詳細に説明します。この回答の目的のために、代替法を破る MonadPlus インスタンスを作成するために >>= を使用できたことは注目に値します。


モノイド構造が便利です。代替は、Applicative Functor にそれを提供する最良の方法です。

于 2012-10-29T02:30:37.800 に答える
4

MonadPlusの法律について意見の相違があるため、MonadPlusについては説明しません。


Applicativeの構造が、Monoidインスタンス*と一致しないAlternativeインスタンスに自然につながる意味のある例を見つけようとして失敗した後、私はついにこれを思いつきました。

結果は内部タイプに依存できないため、Alternativeの法則はMonoidの法則よりも厳密です。これにより、多数のMonoidインスタンスがAlternativeから除外されます。 これらのデータ型は、その種の余分な「構造」によって禁止されている部分的な(つまり、一部の内部型に対してのみ機能する)モノイドインスタンスを許可します* -> *。例:

  • Monoidの標準のMaybeインスタンスは、内部タイプがMonoid=>代替ではないと想定しています。

  • ZipList、タプル、および関数は、それらの内部タイプがモノイド=>代替ではない場合、すべてモノイドにすることができます

  • 少なくとも1つの要素を持つシーケンス-がないため、Alternativesにすることはできませんempty

    data Seq a
        = End a
        | Cons a (Seq a)
      deriving (Show, Eq, Ord)
    

一方、一部のデータ型は、種類が異なるため、代替にすることができません*

  • 単位 - ()
  • Ordering
  • 数値、ブール値

私の推測した結論: AlternativeインスタンスとMonoidインスタンスの両方を持つタイプの場合、インスタンスは同じであることが意図されています。この回答 も参照してください。


たぶん、その標準インスタンスが内部タイプにモノイドを必要としないため、カウントされないと私が主張することを除いて、その場合、それは代替と同じになります

于 2012-10-31T22:44:02.333 に答える
2

オルタナティブ型クラスのポイントは 2 つのものの間で選択することであると理解していましたが、モノイドはものを組み合わせることであると理解していました。

少し考えてみれば、それらは同じです。

+は物 (通常は数値) を結合し、その型シグネチャは(Int -> Int -> Intまたは何でも) です。

演算子は選択肢の中から選択しますが、その<|>型シグネチャも同じです: 2 つの一致するものを取り、組み合わせたものを返します。

于 2012-10-26T12:00:37.867 に答える