Functor / Functor / Applicative / Monadの良い例は?


209

型クラスXが何であるかを誰かに説明しながら、正確にXであるデータ構造の良い例を見つけるのに苦労しています。

だから、私は次の例を要求します:

  • Functorではない型コンストラクター。
  • Functorであるが、Applicativeではない型コンストラクター。
  • Applicativeですがモナドではない型コンストラクタです。
  • モナドである型コンストラクタ。

どこにでもモナドの例はたくさんあると思いますが、前の例とある程度の関係があるモナドの良い例は、絵を完成させることができます。

特定の型クラスに属するために重要な点のみが異なる、互いに類似する例を探します。

アローの例をこの階層のどこかに忍び込むことができたなら(それはApplicativeとMonadの間ですか?)、それも素晴らしいでしょう!


4
適切な* -> *ものが存在しない型コンストラクタ()を作成することは可能fmapですか?
オーウェン

1
オーウェン、私a -> Stringはファンクターではないと思います。
Rotsor

3
@Rotsor @Owen a -> Stringは数学的なファンクターですが、Haskell Functorではありません。
J.アブラハムソン2014

@J。アブラハムソン、それはどのような意味で数学の関手ですか?矢印が逆になっているカテゴリについて話していますか?
Rotsor 2014

3
知らない人のために、反変ファンクターにはタイプのfmapがあります(a -> b) -> f b -> f a
AJFarmar 2015年

回答:


100

Functorではない型コンストラクタ:

newtype T a = T (a -> Int)

それから反変ファンクタを作成できますが、(共変)ファンクタは作成できません。書いてみるfmapと失敗します。反変関数のバージョンが逆になっていることに注意してください。

fmap      :: Functor f       => (a -> b) -> f a -> f b
contramap :: Contravariant f => (a -> b) -> f b -> f a

ファンクタであるが、Applicativeではない型コンストラクタ:

良い例はありません。ありますがConst、理想的には具体的な非モノイドが欲しいので、何も考えられません。基本的に、すべてのタイプは、数値、列挙、積、合計、または関数です。以下のpigworkerをご覧ください。私Data.Voidはであるかどうかについて意見が異なりMonoidます。

instance Monoid Data.Void where
    mempty = undefined
    mappend _ _ = undefined
    mconcat _ = undefined

以来_|_Haskellでは法的値であり、実際には唯一の正当な値Data.Voidこれは、モノイドのルールを満たしています。関数unsafeCoerceを使用するとすぐにプログラムがHaskellのセマンティクスに違反しないことが保証されなくなったため、それが何に関係するのかはわかりませんunsafe

下部の記事(リンク)または安全でない関数(リンク)については、Haskell Wikiを参照してください。

さまざまな拡張機能を備えたAgdaやHaskellなどのよりリッチな型システムを使用して、そのような型コンストラクタを作成できるかどうか疑問に思います。

モナドではなくApplicativeである型コンストラクタ:

newtype T a = T {multidimensional array of a}

あなたはそれからApplicativeを作ることができます:

mkarray [(+10), (+100), id] <*> mkarray [1, 2]
  == mkarray [[11, 101, 1], [12, 102, 2]]

しかし、それをモナドにすると、次元の不一致が発生する可能性があります。このような例は実際にはまれであると思います。

モナドである型コンストラクタ:

[]

矢印について:

Arrowがこの階層のどこにあるかを尋ねるのは、「赤」の形状の種類を尋ねるようなものです。種類の不一致に注意してください。

Functor :: * -> *
Applicative :: * -> *
Monad :: * -> *

だが、

Arrow :: * -> * -> *

3
良いリスト!Either aわかりやすいので、最後のケースの例として、もっと単純なものを使用することをお勧めします。
fuz '28

6
モナドではなくApplicativeである型コンストラクタをまだ探している場合、非常に一般的な例はですZipList
ジョンL

23
_|_*のすべてのタイプにVoid常駐しますが、重要なのは、1つを構築するために後方に曲がる必要があるか、またはその値が破壊されていることです。すでにアカウントをお持ちの場合はこれがなどのEnum、モノイドのなぜそのインスタンスでない、私はあなたがそれらを一緒にマッシュアップさせて幸せだよ(あなたに与えてSemigroup)しかしmempty、私は、明示的に型の値を構築するために工具を与えないVoidvoid。あなたは銃を装填し、それをあなたの足に向け、自分で引き金を引く必要があります。
エドワードKMETT

2
経験的に、Cofunctorの概念は間違っていると思います。ファンクタの双対はファンクタです。なぜなら、入力出力の両方を反転させ、最終的には同じものになるからです。あなたが探している概念はおそらく「反変関数」であり、これは少し異なります。
ベンミルウッド2013年

1
@AlexVong:「非推奨」->人々は別のパッケージを使用しています。混乱して申し訳ありませんが、「双対ファンクター」ではなく「反変ファンクター」について話しています。一部のコンテキストでは、ファンクターは自己双対であるため、「反変ファンクター」を指すために「cofunctor」が使用されているのを見てきましたが、それは単なる混乱を招くようです。
ディートリッヒエップ2016

87

私のスタイルは私の電話で窮屈になっているかもしれませんが、ここに行きます。

newtype Not x = Kill {kill :: x -> Void}

ファンクターになることはできません。もしそうなら、我々は持っているだろう

kill (fmap (const ()) (Kill id)) () :: Void

月はグリーンチーズで作られます。

その間

newtype Dead x = Oops {oops :: Void}

ファンクターです

instance Functor Dead where
  fmap f (Oops corpse) = Oops corpse

ただし、適用できない場合、または

oops (pure ()) :: Void

グリーンはムーンチーズで作られます(実際に起こる可能性がありますが、夜遅くにのみ)。

(補足:Void、in Data.Voidは空のデータ型であるため。これを使用undefinedしてモノイドでunsafeCoerceあることを証明する場合は、それがモノイドでないことを証明するために使用します。)

嬉しいことに

newtype Boo x = Boo {boo :: Bool}

たとえば、ダイクストラがそうであるように、多くの点で適用可能です。

instance Applicative Boo where
  pure _ = Boo True
  Boo b1 <*> Boo b2 = Boo (b1 == b2)

しかし、それはモナドであってはなりません。なぜそうでないかを確認するには、リターンが常にBoo TrueまたはBoo Falseでなければならないことに注意してください。

join . return == id

おそらく保持できません。

ああ、私はほとんど忘れていました

newtype Thud x = The {only :: ()}

モナドです。あなた自身を転がします。

キャッチする飛行機...


8
ボイドは空です!とにかく、道徳。
pigworker

9
Voidはコンストラクタが0の型だと思います。がないため、モノイドではありませんmempty
Rotsor

6
未定義?失礼ですね!悲しいことに、unsafeCoerce(unsafeCoerce()<*> undefined)は()ではないため、実際には、法に違反する観測結果があります。
豚革労働者2011

5
1種類の未定義を許容する通常のセマンティクスでは、あなたの言うとおりです。もちろん、他のセマンティクスもあります。Voidは、フラグメント全体のサブモノイドに限定されません。失敗のモードを区別するセマンティクスのモノイドでもありません。電話ベースの編集よりも簡単に編集できる瞬間があると、私の例は、未定義の種類が1つだけではないセマンティクスでのみ機能することを明確にします。
豚労働者

22
大騒ぎ_|_
ランデイ

71

他の答えはいくつかの単純で一般的な例を見逃していると思います:

FunctorであるがApplicativeではない型コンストラクタ。簡単な例はペアです:

instance Functor ((,) r) where
    fmap f (x,y) = (x, f y)

しかし、にApplicative追加の制限を課さずにインスタンスを定義する方法はありませんr。特に、pure :: a -> (r, a)任意のを定義する方法はありませんr

Applicativeですがモナドではない型コンストラクタです。よく知られている例はZipListです。(newtypeリストをラップしApplicative、それらに異なるインスタンスを提供するのはです。)

fmap通常の方法で定義されます。しかしpure<*>次のように定義されています

pure x                    = ZipList (repeat x)
ZipList fs <*> ZipList xs = ZipList (zipWith id fs xs)

したがってpure、指定された値を繰り返すことによって無限リストを作成し、値<*>のリストを含む関数のリストを圧縮します-i番目の関数をi番目の要素に適用します。(標準<*>では、[]適用のすべての可能な組み合わせ生成し、私に目の機能をJ。番目の要素を)しかし、(参照モナドを定義する方法を何も賢明な方法はありませんこの記事は)。


矢印はファンクタ/アプリケーション/モナド階層にどのように収まるのですか? 参照イディオムはモナドが無差別あり、矢印は細心のある、忘れているサム・リンドレー、フィリップ・ワドラー、ジェレミーYallopで。MSFP2008。(それらは適用ファンクタイディオムと呼ばれます。)要約:

Moggiのモナド、Hughesの矢、McBrideとPatersonのイディオム(適用ファンクタとも呼ばれます)の3つの計算概念の関係を再検討します。イディオムが同型A〜> B = 1〜>(A-> B)を満たす矢印と同等であり、モナドが同型A〜> B = A->(1〜 > B)。さらに、イディオムは矢印に埋め込まれ、矢印はモナドに埋め込まれます。


1
したがって((,) r)、適用可能ではないファンクタも同様です。しかし、これは一般的に一度pureにすべてrを定義することができないからです。それはの一つの定義でのApplicativeファンクタの(無限)集合を定義しようとする、そのための言語の簡潔さの癖だpure<*>。この意味で、この反例については、数学的に何も深いようには見えません。具体的r((,) r) 、具体的なは、適用可能なファンクタになるからです。質問:適用できないコンクリートファンクタについて考えられますか?
ジョージ

1
この質問の投稿として、stackoverflow.com / questions / 44125484 /…を参照してください。
ジョージ

20

ファンクタではないタイプコンストラクタの良い例はSet次のとおりです。fmap :: (a -> b) -> f a -> f b追加の制約Ord bがないと作成できないため、実装できませんf b


16
実際に数学的に私たちがするから良い例です本当にこのファンクタ作るのが好き。
アレクサンドルC.

21
@AlexandreC。私はそれに同意しません、それは良い例ではありません。数学的には、そのようなデータ構造はファンクタを形成します。実装できないのfmapは、単に言語/実装の問題です。また、Set継続モナドにラップすることもできます。これにより、期待するすべてのプロパティを持つモナドができます。この質問を参照してください(効率的に実行できるかどうかはわかりません)。
PetrPudlák12年

@PetrPudlak、これはどのように言語の問題ですか?の等式はb決定できない場合がありますfmap。その場合、定義できません。
Turion

@Turion決定可能であることと定義可能であることは、2つの異なるものです。たとえば、アルゴリズムによってそれを決定することは不可能ですが、ラムダ項(プログラム)の同等性を正しく定義することは可能です。いずれにせよ、これはこの例の場合ではありませんでした。ここでの問題はFunctorOrd制約付きのインスタンスを定義できないことですが、別の定義Functorまたはより優れた言語サポートで可能である可能性があります。実際ConstraintKindsで可能である。このようにパラメータ化することができますタイプのクラスを定義します。
PetrPudlák18年

ord制約を克服できたとしても、Set重複するエントリを含めることができないという事実は、それfmapがコンテキストを祭壇にすることを意味します。これは関連性の法則に違反しています。
John F. Miller

11

この質問に答えるためのより体系的なアプローチを提案し、「ボトム」値や無限データ型などの特別なトリックを使用しない例を示したいと思います。

型コンストラクターが型クラスインスタンスを保持できないのはいつですか?

一般に、型コンストラクターが特定の型クラスのインスタンスを保持できない理由は2つあります。

  1. 型クラスから必要なメソッドの型シグネチャを実装できません。
  2. 型シグネチャを実装できますが、必要な法律を満たすことができません。

第1の種類の例は、第2の種類の例よりも簡単です。第1の種類については、特定の型シグネチャで関数を実装できるかどうかを確認する必要があるだけですが、第2の種類については、実装がないことを証明する必要があるためです。おそらく法律を満たすことができます。

具体例

  • 型を実装できないためにファンクターインスタンスを持つことができない型コンストラクター

    data F z a = F (a -> z)

これは、型パラメーターに関して、ファンクターではなく対偶関数です。 aa反変の位置にあるためです。型シグネチャを持つ関数を実装することは不可能(a -> b) -> F z a -> F z bです。

  • 合法的なファンクタではない型コンストラクタシグネチャをfmap実装できても:

    data Q a = Q(a -> Int, a)
    fmap :: (a -> b) -> Q a -> Q b
    fmap f (Q(g, x)) = Q(\_ -> g x, f x)  -- this fails the functor laws!

この例の好奇心の側面は、私たちがいることであることができます実装しfmapていても正しいタイプのFそれが使用しているので、おそらくファンクタすることができないa反変な位置に。したがって、上にfmap示したこの実装は誤解を招くものです-正しい型シグネチャを持っていますが(これはその型シグネチャの唯一の可能な実装であると思います)、ファンクターの法則は満たされていません。たとえば、fmap ididはなのでlet (Q(f,_)) = fmap id (Q(read,"123")) in f "456"ですが123let (Q(f,_)) = id (Q(read,"123")) in f "456"です456

実際にFは、それは単なるファンクターであり、ファンクターでも対ファンクターでもありません。

  • の型シグネチャをpure実装できないため適用さない合法的なファンクター:Writerモナド(a, w)を取得しw、モノイドであるべき制約を削除します。その場合、から型の値を構築することは不可能(a, w)ですa

  • の型シグネチャを<*>実装できないため、適用できないファンクタdata F a = Either (Int -> a) (String -> a)

  • 型クラスのメソッドを実装することはできても、合法的ではないファンクタ

    data P a = P ((a -> Int) -> Maybe a)

型コンストラクターPは、a共変な位置でのみ使用されるため、ファンクターです。

instance Functor P where
   fmap :: (a -> b) -> P a -> P b
   fmap fab (P pa) = P (\q -> fmap fab $ pa (q . fab))

の型シグネチャの可能な実装は、<*>常に次を返す関数のみですNothing

 (<*>) :: P (a -> b) -> P a -> P b
 (P pfab) <*> (P pa) = \_ -> Nothing  -- fails the laws!

しかし、この実装は、アプリケーションファンクタのアイデンティティ法を満たしていません。

  • ApplicativeMonadの型シグネチャが原因であるが、そうではないファンクタbind実装できない。

私はそのような例を知りません!

  • ApplicativeないファンクタMonad法律がの型署名があっても満足することができないため、bind実現することができます。

この例はかなりの議論を引き起こしているので、この例が正しいことを証明することは容易ではないと言っても安全です。しかし、何人かの人々がこれを異なる方法で独立して検証しました。`data PoE a =空です|を参照してください。ペアaa`モナド?追加の議論のため。

 data B a = Maybe (a, a)
   deriving Functor

 instance Applicative B where
   pure x = Just (x, x)
   b1 <*> b2 = case (b1, b2) of
     (Just (x1, y1), Just (x2, y2)) -> Just((x1, x2), (y1, y2))
     _ -> Nothing

適法なMonad事例がないことを証明するのはやや面倒です。非モナドの振る舞いの理由は、bind関数f :: a -> B bが返すことができるときに実装する自然な方法がないためですNothingまたはJustの異なる値に対してためですa

これMaybe (a, a, a)もモナドではないことを考慮して、そのjoinための実装を試みることはおそらくより明確です。直感的に実装できる合理的な方法がないことに気付くでしょうjoin

 join :: Maybe (Maybe (a, a, a), Maybe (a, a, a), Maybe (a, a, a)) -> Maybe (a, a, a)
 join Nothing = Nothing
 join Just (Nothing, Just (x1,x2,x3), Just (y1,y2,y3)) = ???
 join Just (Just (x1,x2,x3), Nothing, Just (y1,y2,y3)) = ???
 -- etc.

で示されるケースでは、タイプの6つの異なる値から合理的かつ対称的な方法で???生成できないことは明らかです。確かに、これらの6つの値の任意のサブセットを選択できます。たとえば、常に最初の空でないJust (z1, z2, z3)aMaybeが、これはモナドの法則を満たしません。戻るNothingことも法律を満たさないでしょう。

  • 連想性があってもモナドではないツリー状のデータ構造bindが、恒等法に違反。

通常のツリーのようなモナド(または「関手型の枝を持つツリー」)は、次のように定義されます。

 data Tr f a = Leaf a | Branch (f (Tr f a))

これは関手上の無料モナドです fです。データの形状はツリーであり、各分岐点はサブツリーの「機能豊富」です。標準の二分木はで取得されtype f a = (a, a)ます。

葉もファンクターの形にしてこのデータ構造を変更するfと、「セミモナド」と呼ばれるものが得られます。bindこれは、自然性と結合性の法則を満たしていpureますが、その方法は恒等法の1つに失敗します。「セミモナドはエンドファンクターのカテゴリーのセミグループですが、何が問題なのですか?」これは型クラスBindです。

簡単にするために、次のjoin代わりにメソッドを定義しますbind

 data Trs f a = Leaf (f a) | Branch (f (Trs f a))
 join :: Trs f (Trs f a) -> Trs f a
 join (Leaf ftrs) = Branch ftrs
 join (Branch ftrstrs) = Branch (fmap @f join ftrstrs)

枝の接ぎ木は標準ですが、葉の接ぎ木は非標準であり、 Branchます。これは連想法の問題ではありませんが、同一性法の1つを破ります。

多項式型にはいつモナドインスタンスがありますか?

ファンクタのどちらMaybe (a, a)Maybe (a, a, a)合法与えることができMonad、彼らは明らかにされているものの、インスタンスをApplicative

なし-これらのファンクタにはトリックがないVoidか、bottomどこでも、ないトリッキーな怠惰/厳しさ、いや無限の構造、およびなし型クラスの制約を。Applicativeインスタンスは、完全に標準です。機能returnとは、bindこれらのファンクタのために実装することができますが、モナドの法則を満たしています。言い換えると、特定の構造が欠落しているため、これらのファンクタはモナドではありません(しかし、何が欠落しているのかを正確に理解することは容易ではありません)。例として、ファンクターの小さな変更はそれをモナドにすることができます:data Maybe a = Nothing | Just aはモナドです。別の同様のファンクターdata P12 a = Either a (a, a)もモナドです。

多項式モナドの構築

一般的に、Monad多項式タイプから合法的なを生成するいくつかの構文があります。これらのすべての構造で、Mはモナドです。

  1. type M a = Either c (w, a) どこ w任意のモノイドであります
  2. type M a = m (Either c (w, a))どこm任意のモナドであり、w任意のモノイドであります
  3. type M a = (m1 a, m2 a)モナドはどこにありm1m2
  4. type M a = Either a (m a)どこm任意のモナドであります

最初の構造はWriterT w (Either c)、2番目の構造はWriterT w (EitherT c m)です。:第3の構成は、モナドの成分ごとの積であるpure @Mの成分ごとの積として定義されるpure @m1pure @m2、およびjoin @M(例えば、クロス積データを省略して定義されているm1 (m1 a, m2 a)にマッピングされm1 (m1 a)たタプルの第二の部分を省略することによって)。

 join :: (m1 (m1 a, m2 a), m2 (m1 a, m2 a)) -> (m1 a, m2 a)
 join (m1x, m2x) = (join @m1 (fmap fst m1x), join @m2 (fmap snd m2x))

4番目の構造は次のように定義されます

 data M m a = Either a (m a)
 instance Monad m => Monad M m where
    pure x = Left x
    join :: Either (M m a) (m (M m a)) -> M m a
    join (Left mma) = mma
    join (Right me) = Right $ join @m $ fmap @m squash me where
      squash :: M m a -> m a
      squash (Left x) = pure @m x
      squash (Right ma) = ma

4つの構造すべてが合法的なモナドを生成することを確認しました。

多項式モナドには他の構造はないと推測します。たとえば、ファンクタMaybe (Either (a, a) (a, a, a, a))はこれらの構造のいずれからも取得されないため、モナドではありません。しかし、Either (a, a) (a, a, a)それは3つのモナドの製品と同型であるからモナドであるaaMaybe a。また、Either (a,a) (a,a,a,a)それはの製品と同型であるため、モナドであるaEither a (a, a, a)

上記の4つの構造によりa、たとえばEither (Either (a, a) (a, a, a, a)) (a, a, a, a, a))、任意の数のの任意の数の製品の任意の合計を取得できます。そのような型コンストラクターはすべて(少なくとも1つ)Monadインスタンスがあります。

もちろん、そのようなモナドにはどのようなユースケースが存在するのかはまだわかりません。別の問題は、Monad構造1〜4を介して導出されたインスタンスが一般に一意ではないことです。たとえば、型コンストラクタにtype F a = Either a (a, a)Monad2つの方法でインスタンスを与えることができます。モナドを使用する構築4 (a, a)と、型同型を使用する構築3 Either a (a, a) = (a, Maybe a)です。繰り返しますが、これらの実装のユースケースを見つけることはすぐには明らかではありません。

問題は残っています-任意の多項式データ型が与えられたMonad場合、それがインスタンスを持っているかどうかをどのように認識するかです。多項式モナドに他の構造がないことを証明する方法がわかりません。今のところ、この質問に答える理論は存在しないと思います。


私が考えてB いるモナド。このバインドの反例を挙げられますPair x y >>= f = case (f x, f y) of (Pair x' _,Pair _ y') -> Pair x' y' ; _ -> Emptyか?
フランキー

@Franky関連性は、あなたが選択したこの定義で失敗fなどf xであるEmptyが、f yあるPairし、次のステップの両方ですPair。この実装やその他の実装には法律が適用されないことを手で確認しました。しかし、それを行うのはかなりの作業です。これを理解する簡単な方法があったらいいのにと思います。
winitzki

1
@Turionには、心配するさまざまな値が含まれていないMaybeため、この引数は適用されMaybeませんa
Daniel Wagner

1
@Turion数ページの計算でこれを証明しました。「自然な方法」に関する議論は、単なる発見的説明です。Monadインスタンスは、関数で構成returnし、bind満足の法則という。の2つの実装returnbind、必要なタイプに適合する25の実装があります。直接計算により、どの実装も法則を満たさないことを示すことができます。必要な作業量を減らすために、私はアイデンティティ法のjoin代わりにbind使用し、最初にアイデンティティ法を使用しました。しかし、それはかなりの作業でした。
winitzki 2018

1
@duplodeいいえ、Traversable必要ではないと思います。m (Either a (m a))はに変換pure @mされm (Either (m a) (m a))ます。その後、ささいにEither (m a) (m a) -> m a、そして私たちは使用できますjoin @m。それが私が法律をチェックした実装でした。
winitzki
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.