まず、これらの質問のそれぞれに簡単に答えてみましょう。次に、それぞれをより詳細な回答に展開しますが、これらの短い回答がそれらをナビゲートするのに役立つことを願っています.
いいえ、別の意味ではAlternative
ありません。は、と の両方の構造を持つ型用です。「選択」と「組み合わせ」は、同じ広い概念に対する 2 つの異なる直感です。Monoid
Alternative
Applicative
Monoid
Alternative
これはempty
、<|>
設計者がこれが便利だと考えたからであり、これがモノイドを生成するからです。ピッキングで言えばempty
、ありえない選択をすることに相当します。
前者は後者よりも多くの法律に従う (またはすべき) ため、Alternative
両方が必要です。これらの法則は、型コンストラクターのモノイドおよび適用可能な構造に関連しています。さらに、内側の型に依存することはできませんが、依存することはできます。Monoid
Alternative
Monoid
MonadPlus
Alternative
より多くの法則に従う必要があるため、よりもわずかに強力です。これらの法則は、適用可能な構造に加えて、モノイド構造をモナド構造に関連付けます。両方のインスタンスがある場合、それらは一致するはずです。
Alternative
とは全然違う意味じゃないMonoid
ですか?
あまり!あなたの混乱の理由の 1 つは、HaskellMonoid
クラスがかなり悪い (一般的ではない) 名前を使用していることです。これは、数学者がモノイドを定義する方法です (それについて非常に明示的です)。
意味。モノイドは、区別された要素 ε ∈ M と二項演算子 · : M × M → M を備えた集合Mであり、並置で表され、次の2つの条件が成り立ちます。
- εは単位元です。すべての m ∈ Mに対して、m ε = ε m = mです。
- · 連想: すべての 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 の間の選択に対応します。これが「ピッキング」の直感です。どちらもモノイドの法則に従うことに注意してください。
- 何も持たないこと、選択の余地がないこと、それがアイデンティティです。
- 何も持っていないのに何かと一緒にグロムすると、また同じものになってしまいます。
- まったく選択肢がない (不可能なこと) か、他の選択肢がある場合、他の (可能な) 選択肢を選ばなければなりません。
- コレクションをまとめて選択することは、どちらも連想的です。
- 3 つのコレクションがある場合、最初の 2 つをまとめてから 3 つ目をグロム化するか、最後の 2 つをまとめてから最初のコレクションをグロム化するかは問題ではありません。いずれにせよ、最終的には同じ総グロム コレクションになります。
- 3 つの選択肢がある場合、(a) 最初に 1 番目または 2 番目と 3 番目のいずれかを選択し、必要に応じて 1 番目と 2 番目のいずれかを選択するか、(b) 最初に 1 番目から選択するかは問題ではありません2 番目または 3 番目、そして必要に応じて 2 番目と 3 番目の間。いずれにせよ、私は好きなものを選ぶことができます。
(注: 私はここで速くてゆるく遊んでいます。それが直感である理由です。たとえば、次のことを覚えておくことが重要です。 <em>m₁.)
見よ: これらの種類の両方 (および他の多くの数 — 乗算は、実際には「結合」または「選択」のどちらかですか?) は同じ規則に従います。直感を持つことは理解を深めるために重要ですが、実際に何が起こっているかを決定するのはルールと定義です。
そして最良の部分は、これらの両方の直感が同じキャリアによって解釈できることです! Mを空集合を含む集合の集合 (すべての集合の集合ではない!)とし、 εを空集合 ∅ とし、 · 集合和∪ とする。∅ が ∪ の単位元であり、∪ が結合的であることは簡単にわかります。したがって、( M ,∅,∪) はモノイドであると結論付けることができます。今:
- 集合を物事の集まりと考えると、∪ はそれらをまとめてより多くのものを得る、つまり「組み合わせる」直観に対応します。
- セットを可能なアクションを表すものと考えると、∪ は選択可能なアクションのプールを増やすことに対応します。つまり、「選択」の直感です。
これはまさに[]
Haskell で行われていることです:[a]
はMonoid
for alla
であり[]
、アプリケーション ファンクター (およびモナド) として非決定性を表すために使用されます。結合とピッキングの両方の直観は、同じタイプで一致します:mempty = empty = []
とmappend = (<|>) = (++)
.
したがって、Alternative
クラスは、(a) アプリカティブ ファンクターであり、(b) 型でインスタンス化されると、いくつかの規則に従う値とバイナリ関数を持つオブジェクトを表すためだけに存在します。どのルール?モノイド規則。なんで?それは有用であることが判明したため:-)
Alternative
空のメソッド/メンバーが必要なのはなぜですか?
ええと、皮肉な答えは「Alternative
モノイド構造を表しているからです」です。しかし、本当の問題は、なぜモノイド構造なのかということです。なぜ半群、 εのないモノイドではないのでしょうか? 答えの 1 つは、モノイドの方が便利だと主張することです。多くの人 (おそらくEdward Kmettではない) がこれに同意すると思います。ほとんどの場合、適切な(<|>)
/ / · があれば、適切な// εmappend
を定義できます。一方で、より多くのものを傘の下に置くことができるので、余分な一般性を持つことは良いことです.empty
mempty
また、これが「ピッキング」の直感とどのように噛み合っているかを知りたいと考えています。ある意味では、「『ピッキング』の直感をいつ捨てるかを知ること」が正解であることを念頭に置いて、この 2 つを統合することができると思います。非決定論の適用的関手 を考えてみましょう[]
。type の 2 つの値を と組み合わせる[a]
と(<|>)
、左からのアクションまたは右からのアクションのいずれかを非決定論的に選択することに対応します。しかし、場合によっては、一方の側で可能なアクションがまったくない場合もありますが、それは問題ありません。同様に、パーサーについて考えると、(<|>)
左側または右側にあるものを解析するパーサーを表します (「選択」)。そして、常に失敗するパーサーがある場合、それは ID になります。それを選択すると、すぐにその選択を拒否し、別のパーサーを試します。
とは言っても、ほぼ に似ているが を欠いているクラスを持つことは完全に可能であることを覚えておいてください。それは完全に有効であり、のスーパークラスである可能性もありますが、たまたま Haskell が行ったことではありません。おそらく、これは何が役立つかについての推測ではありません。Alternative
empty
Alternative
Alternative
型クラスにApplicative
制約が必要なのはなぜですか* -> *
? … [使用] だけではないのはなぜliftA2 mappend
ですか?
Applicative
さて、これら 3 つの提案された変更のそれぞれについて考えてみましょうAlternative
。Alternative
の引数の種類を変更します。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) -> a
Monoid
Alternative
f
f
Alternative
ある種の型クラスとして残したいと考えているので、それを変更するための 2 つの提案された方法を見てみましょう。種類を変更する場合は、制約を取り除く必要があります。親切なことしか話さないので、それを参照する方法はありません。これにより、2 つの変更が可能になります。最初の、よりマイナーな変更は、制約を取り除き、種類をそのままにしておくことです。Applicative
Applicative
* -> *
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''
Semigroup
Applicative
Alternative
forall a. Monoid (f a)
Alternative
つまり、問題は「パーツと、両方のインスタンスである のパーツとの間に関係があるか」ということになりAlternative
ます。そして、ドキュメントには何もありませんが、私は立場に立ってイエスと言うつもりです— または、少なくとも、ドキュメントがあるべきです. (モノイドの法則に加えて) に関連するいくつかの法則に従うことになっていると思います。特に、それらの法律は次のようなものだと思いますApplicative
f
Alternative
Applicative
- 右分配 (の
<*>
): (f <|> g) <*> a = (f <*> a) <|> (g <*> a)
- 右吸収 ( の場合
<*>
): empty <*> a = empty
- 左分配係数 (の
fmap
): f <$> (a <|> b) = (f <$> a) <|> (f <$> b)
- 左吸収 ( の場合
fmap
): f <$> empty = empty
[]
これらの法則はと、 および (そのインスタンスがインスタンスであるMaybe
ふりをする)に当てはまるように見えますが、証明や徹底的なテストは行っていません。(例えば、私は当初、分配性は に対して保持されると考えていたが、これは に対して間違った順序で「効果を実行する」。) しかし類推すると、 が同様の法則に従うことが期待されるのは事実である (明らかにいくつかの法則があるが、どちらについてのあいまいさ)。私はもともと第三法則を主張したかったのですが、それは当然のことのようです。MonadPlus
Alternative
IO
<*>
[]
MonadPlus
- 左吸収 ( の場合
<*>
): a <*> empty = empty
しかし、私はこの法則を信じ[]
て従っていますが、そうではありません。(次の段落で明らかになる理由により) それを要求しないのが最善だと思います。Maybe
IO
実際、Edward Kmettも同様の見解を支持するスライドをいくつか持っているようです。それに入るには、もう少し数学的な専門用語を含む簡単な余談をする必要があります。最後のスライド「I Want More Structure」では、「Monoid は Applicative に対すAlternative の引数を取り除くと、RightSemiNearRing が得られます。」</p>
正しいセミナー?「正しいセミナーはどのようにしてそこに入ったのですか?」私はあなたが泣くのを聞きます。 良い、
意味。右ニアセミリング ( これも右セミニアリングですが、Google では前者の方がよく使われているようです) は四重項 ( R ,+,·,0) であり、ここで ( R ,+,0) はモノイド ( R ,·)です。は半群であり、次の 2 つの条件が成り立ちます。
- · すべてのr , s , t ∈ R , ( s + t ) r = sr + trに対して、 + の上で右分配です。
- 0 は · に対して右吸収です: すべてのr ∈ Rに対して、0 r = 0。
左近セミリングも同様に定義されます。
は真の連想演算子でも二項演算子でもないため<*>
、型が一致しません。これは、エドワード・クメットが「議論を捨てる」ことについて話しているときに得ていることだと思います。f a
別のオプションは、実際には ( , <|>
, <*>
, ) が半リンゴイドに近い右側empty
を形成することを望んでいる (これが正しいかどうかはわかりません) と言うかもしれません。要素の特定のペア (グループイド風)。また、( , , , ) は左に近い半りんご形であるとも言いたいのですが、これはおそらくf a
<|>
<$>
empty
Applicative
法則と右半りんご形に近い構造。しかし今、私は頭を悩ませています。とにかく、これは深く関係していません.
いずれにせよ、これらの法則はモノイドの法則よりも強力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)
すべての型に対してモノイド構造が存在するわけではなくb
、Alternative
のモノイド構造は内部型不可知論的でなければならないからです。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 で時折型注釈を付けて確認できます)。
- 右分配率:
(ssf 1 <> ssf 1) <*> ssn 1 = (Sum 3, Sum 4)
(ssf 1 <*> ssn 1) <> (ssf 1 <*> ssn 1) = (Sum 4, Sum 4)
- 右吸収:
mempty <*> ssn 1 = (Sum 1, Sum 0)
mempty = (Sum 0, Sum 0)
- 左分配率:
(<> Sum 1) <$> (ssn 1 <> ssn 1) = (Sum 2, Sum 3)
((<> Sum 1) <$> ssn 1) <> ((<> Sum 1) <$> ssn 1) = (Sum 2, Sum 4)
- 左吸収:
(<> Sum 1) <$> mempty = (Sum 0, Sum 1)
mempty = (Sum 1, Sum 1)
次に、 を考えMaybe
ます。Maybe
現状では、Monoid
とAlternative
インスタンスは一致しません。(このセクションの冒頭で言及した haskell-cafe の議論では、これを変更することが提案されていますが、同じ効果をもたらすsemigroups パッケージのOption
newtype があります。 ) 基本パッケージには半群クラスがないため、モノイドを持ち上げるだけなので、次のようになります (基本パッケージの sourceを再フォーマットします):Monoid
Maybe
Nothing
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
<*>
Alternative
Monoid
fmap
fmap
Alternative
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 つの潜在的な法則セットがあるという事実によって、問題は複雑になります。がとでモノイドを形成することになっていることは普遍的に合意されており、左ゼロの法則を満たすことになっています。Monad
Applicative
MonadPlus
Alternative
Monoid
MonadPlus
Alternative
empty <*> a
empty >>= f
Maybe
MonadPlus
MonadPlus
mplus
mempty
mempty >>= f = mempty
. ただし、一部のMonadPlus
ses は左分布, 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 ラッパーを想像できます。Alternative
a <|> Just b = Just b
. これは、左分布も左キャッチも満たさないでしょうが、完全に有効なAlternative
.
ただし、あるタイプはそのMonadPlus
インスタンスとそのインスタンスが一致する必要があるため (これは、 s であるsに対して等しいAlternative
ことが必要であるのと同じように必要であると私は信じていap
ます)、代わりにクラスを次のように定義することを想像できます。(<*>)
Monad
Applicative
MonadPlus
class (Monad m, Alternative m) => MonadPlus' m
クラスは新しい関数を宣言する必要はありません。empty
これは、指定された型が従う法則についての単なる約束(<|>)
です。この設計手法は、Haskell 標準ライブラリでは使用されていませんが、同様の目的で、より数学的なパッケージで使用されています。たとえば、格子パッケージは、格子が吸収法則によってリンクされている同じタイプの結合半格子と会合半格子であるという考えを表現するためにそれを使用します。
常に一致することをAlternative
保証したい場合でも、に対して同じことを行うことができない理由は、種類の不一致のためです。目的のクラス宣言の形式は次のとおりです。Alternative
Monoid
class (Applicative f, forall a. Monoid (f a)) => Alternative''' f
しかし (上で述べたように) GHC Haskell でさえ、定量化された制約をサポートしていません。
また、Alternative
のスーパークラスにMonadPlus
なるには のスーパークラスである必要Applicative
があることに注意してくださいMonad
。その問題に遭遇した場合、明らかな方法でanyを にWrappedMonad
変える newtypeが常に存在します。あなたが期待することを正確に行う があります。Monad
Applicative
instance MonadPlus m => Alternative (WrappedMonad m) where ...