web-dev-qa-db-ja.com

「代替」型クラスの意味と他の型クラスとの関係に混乱

私は型クラスを学ぶために Typeclassopedia を調べてきました。 Alternative(およびMonadPlus、さらに言えば)を理解できません。

私が抱えている問題:

  • 'pediaは、「Alternative型クラスは、モノイド構造も持つApplicativeファンクタのためのものです。これはわかりません-AlternativeはMonoidとはまったく異なる何かを意味するのではないですか?つまり、Alternative型クラスのポイントは2つのものを選択することとして理解しましたが、Monoidsはものを結合することについて理解しました。

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

  • alternativeタイプのクラスにApplicative制約が必要な理由と、* -> *の種類が必要な理由なぜ<|> :: a -> a -> aだけではないのですか?すべてのインスタンスを同じ方法で実装することはできますが…(わかりません)。 Monoidが提供しない価値は何ですか?

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

うまくいけば、これらすべての質問が首尾一貫しています...!


バウンティの更新:@Antalの回答は素晴らしいスタートですが、Q3はまだオープンです:AlternativeはMonoidが提供しないものを何が提供しますか?具体的な例がないため、 この答え は不十分であり、Alternativeの優しさがそれをMonoidとどのように区別するかについての具体的な議論です。

Applicativeの効果をMonoidの動作と組み合わせる場合は、次の理由だけではありません。

liftA2 mappend

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

だからこそ私はAlternativeが必要な理由と、それがMonoidとどのように異なるか、または何か別のものを意味するかを示す特定の例を探しています。

62
Matt Fenwick
_import Data.Monoid
import Control.Applicative
_

MonoidとAlternativeがMaybeファンクタとZipListファンクタとどのように相互作用するかの例をたどっていきましょう。タブを切り替えることから常にハッキングのビットに切り替えることですが、主にそうすることができます 過去のghciを実行する タイプミスを修正する

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

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

_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 []_であるかのように扱うことにより、本来はおそらくPerhapsレベルではなく、文字列レベルで機能します。 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の構造を使用して、全体に構造を追加しました。 Setsを組み合わせている場合は、代わりに_Ord a_コンテキストを追加できます。

ZipList clone

では、elementsとzipListをどのように組み合わせる必要がありますか?これらを組み合わせる場合、これらのZipは何にすべきですか?

_   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を使用して要素を組み合わせる唯一の賢明な方法です。そのため、これは唯一の賢明なモノイドインスタンスです。

興味深いことに、HaskellはIntsを組み合わせる方法を知らないため、上記のMaybeの例では機能しません。_+_または_*_を使用する必要がありますか?数値データのMonoidインスタンスを取得するには、それらをSumまたはProductでラップして、使用するモノイドを指示します。

_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 a_コンテキストをここに置くことができるものです- _Eq a_または_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 clone

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

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

要素で_<|>_を使用するのは非常に魅力的ですが、要素のタイプを使用できないため、使用できません。 emptyから始めましょう。 Alternativeを定義するときに要素のタイプがわからないため、要素を使用できません。そのため、_Zip []_にする必要があります。 _<|>_の左(できれば右)のIDである必要があるため、

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

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

  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
_

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

キーポイント

通知Alternativeは種類_* -> *_のコンストラクターをとるため、可能な方法はありません_Ord a_または_Eq a_または_Monoid a_コンテキストを追加します。代替は、構造内のデータに関する情報を使用することは許可されていません。どれだけやりたいとしても、データをdoすることはできません。

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

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

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

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

MayidのMonoidインスタンスには何の問題もありません。それは、combiningデータを処理することです。
MaybeのAlternativeインスタンスには何も問題はありません-それは仕事をしていて、物事の間で選択しています。

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

両方ができるのは良いことです。

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

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

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

Alternativeにemptyがあるのはなぜですか?

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

それはなぜ追加に0が必要なのかを尋ねるようなものです-何かを追加したい場合、何も追加しないものを持っていることの意味は何ですか?答えは、0はすべてがさらに回転する十字の重要な数値であり、1が乗算に重要であるのと同じように、_[]_はリストに重要です(そして_y=e^x_は微積分に重要です)。実際には、これらの何もしない要素を使用して構築を開始します。

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

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

monadPlus型クラスのポイントは何ですか?モナドとオルタナティブの両方として何かを使用するだけで、そのすべての良さのロックを解除することはできませんか?どうしてそれを捨てないのですか? (私は間違いだと確信していますが、反例はありません)

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

あなたの興味深い質問で、Antal S-Z、PetrPudlák、そして私はMonadPlusとApplicativeの関係が本当に何であるかを掘り下げました。答え here および here は、MonadPlus(左側の分布の意味で-詳細はリンク先)であるものもすべてAlternative、しかしその逆ではありません。

これは、MonadとMonadPlusのインスタンスを作成すると、 とにかくApplicativeおよびAlternativeの条件を満たします であることを意味します。これは、MonadPlusのルール(左distを使用)に従う場合、MonadをApplicativeにし、Alternativeを使用している可能性があることを意味します。

ただし、MonadPlusクラスを削除すると、ルールを文書化するための適切な場所が削除され、MonadPlusでなくても何かを代替として指定することができなくなります(技術的にはMaybeのために行うべきでした)。これらは理論的な理由です。実用的な理由は、既存のコードを破壊することです。 (これは、ApplicativeもFunctorもモナドのスーパークラスではない理由でもあります。)

AlternativeとMonoidは同じではありませんか?オルタナティブとモノイドは完全に違うのではないですか?

'pediaは、「Alternative型クラスは、モノイド構造も持つApplicativeファンクタのためのものです。これはわかりません-AlternativeはMonoidとはまったく異なる何かを意味するのではないですか?つまり、Alternative型クラスのポイントは2つのものを選択することとして理解しましたが、Monoidsはものを結合することについて理解しました。

MonoidとAlternativeは、1つのオブジェクトを2つのオブジェクトから適切な方法で取得する2つの方法です。 Mathsは、データを選択、結合、混合、または拡大するかどうかを考慮しません。そのため、AlternativeはApplicativeのモノイドと呼ばれていました。あなたは今その概念に慣れているようですが、今あなたは言う

alternativeとMonoidの両方のインスタンスを持つタイプの場合、インスタンスは同じであることが意図されています

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

16
AndrewC

概要

  • 一部のアプリケーションファンクタのモノイドインスタンスを定義する必要があります。これらのインスタンスは、より低いレベルのモノイドを単に持ち上げるだけでなく、アプリケーションファンクタレベルで純粋に結合します。以下の_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
_

意味:uppercase文字を解析し、次に_many alphaNum_ eric文字を解析して、結果を純粋な関数_(:)_で単一の文字列に結合します。その後、純粋な関数Literalを適用して、これらのStringsをTypesに変換します。 lowercase文字で始まることを除いて、変数の型をまったく同じ方法で解析します。

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

それはすばらしいことであり、期待通りの_parseTest literal "Bool" == Literal "Bool"_です。

質問3a:applicativeの効果をMonoidの動作と組み合わせる場合は、なぜ_liftA2 mappend_でないのか

編集:エラー-実際に_<|>_を使用するのを忘れた!
次に、Alternativeを使用してこれら2つのパーサーを結合しましょう。

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

これは、任意のタイプを解析できます:_parseTest types "Int" == Literal "Bool"_および_parseTest types "a" == Variable "a"_。これは、2つのではなく、2つのパーサーを組み合わせます。これが、データレベルではなくアプリケーションファンクタレベルで機能するという意味です。

しかし、私たちが試した場合:

_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_の種類は_*_であるため、そのように言うのは好きではありませんが、ParsersではなくTypesを組み合わせていると言うのは本当です。

(Monoidインスタンスを持つ型であっても、_liftA2 mappend_は_<|>_と同じパーサーを提供しません。_Parser String_で試してみると、解析する_liftA2 mappend_が得られます_<|>_は最初のパーサーを試行し、失敗した場合はデフォルトで2番目のパーサーを使用します。

質問3b:Alternativeの_<|> :: f a -> f a -> f a_はMonoidの_mappend :: b -> b -> b_とどのように異なりますか?

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

ただし、2番目に、Monoidを直接使用すると問題が発生します。mappendと同じ構造であることを示すと同時に、パーサーで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)'
_

したがって、アプリケーションファンクタfがある場合、Alternativeインスタンスは、_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 Functorsの単なるMonoidです(そして、Monoidの単なるリフトではありません)。言語拡張なしで定義できるように、種類の高い_f a -> f a -> f a_が必要です。

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

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

  2. monadPlus型クラスのポイントは何ですか?モナドとオルタナティブの両方として何かを使用するだけで、そのすべての良さのロックを解除することはできませんか?いいえ。 リンクした質問 に戻ります。

さらに、Applicativeがモナドのスーパークラスであったとしても、_empty <*> m = empty_を遵守するだけでは_empty >>= f = empty_を証明するのに厳密ではないため、とにかくMonadPlusクラスが必要になります。

....そして私は例を思いついた:多分。私は、Antalの質問に this answer で証明して詳細に説明します。この回答の目的上、>> =を使用して、代替法則に違反するMonadPlusインスタンスを作成できたことは注目に値します。


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

8
AndrewC

私はAlternativeタイプのクラスのポイントを2つのものの間で選択することとして理解しましたが、Monoidはものを組み合わせることについて理解しました。

ちょっと考えてみれば同じです。

+は物事(通常は数値)を組み合わせたもので、型シグネチャはInt -> Int -> Int(または何でも)。

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

2