it-swarm.dev

Gute Beispiele für Not a Functor / Functor / Applicative / Monad?

Während ich jemandem erkläre, was eine Typklasse X ist, habe ich Mühe, gute Beispiele für Datenstrukturen zu finden, die genau X sind.

Also bitte ich um Beispiele für:

  • Ein Typkonstruktor, der kein Functor ist.
  • Ein Typkonstruktor, der ein Functor, aber kein Applicative ist.
  • Ein Typkonstruktor, der ein Applicative ist, aber keine Monade.
  • Ein Typkonstruktor, der eine Monade ist.

Ich denke, es gibt überall viele Beispiele für Monade, aber ein gutes Beispiel für Monade mit einem gewissen Bezug zu früheren Beispielen könnte das Bild vervollständigen.

Ich suche nach Beispielen, die einander ähnlich sind und sich nur in Aspekten unterscheiden, die für die Zugehörigkeit zu einer bestimmten Typenklasse wichtig sind.

Wenn man es schaffen könnte, irgendwo in dieser Hierarchie ein Beispiel für Arrow zu finden (zwischen Applicative und Monad?), Wäre das auch großartig!

197
Rotsor

Ein Typkonstruktor, der kein Functor ist:

newtype T a = T (a -> Int)

Sie können einen kontravarianten Funktor daraus machen, aber keinen (kovarianten) Funktor. Versuchen Sie, fmap zu schreiben, und Sie werden scheitern. Beachten Sie, dass die Version des kontravarianten Funktors umgekehrt ist:

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

Ein Typkonstruktor, der ein Funktor ist, aber nicht anwendbar:

Ich habe kein gutes Beispiel. Es gibt Const, aber im Idealfall hätte ich gerne ein konkretes Nicht-Monoid und mir fällt keines ein. Bei allen Typen handelt es sich im Grunde genommen um Zahlen, Aufzählungen, Produkte, Summen oder Funktionen. Sie können unter Schweinearbeiter sehen und ich bin nicht einverstanden, ob Data.Void ist ein Monoid;

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

Schon seit _|_ ist ein gesetzlicher Wert in Haskell, und tatsächlich der einzige rechtliche Wert von Data.Void, dies entspricht den Monoid-Regeln. Ich bin mir nicht sicher, was unsafeCoerce damit zu tun hat, da Ihr Programm nicht mehr garantiert keine Haskell-Semantik verletzt, sobald Sie eine Funktion unsafe verwenden.

Im Haskell-Wiki finden Sie einen Artikel unten ( link ) oder unsichere Funktionen ( link ).

Ich frage mich, ob es möglich ist, einen solchen Typkonstruktor mit einem reichhaltigeren Typsystem wie Agda oder Haskell mit verschiedenen Erweiterungen zu erstellen.

Ein Typkonstruktor, der ein Applicative, aber keine Monad ist:

newtype T a = T {multidimensional array of a}

Sie können daraus einen Bewerber machen, mit etwas wie:

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

Wenn Sie es jedoch zu einer Monade machen, kann es zu einem Dimensionsfehler kommen. Ich vermute, dass solche Beispiele in der Praxis selten sind.

Ein Typkonstruktor, der eine Monade ist:

[]

Über Pfeile:

Zu fragen, wo ein Pfeil auf dieser Hierarchie liegt, ist wie zu fragen, was für eine Form "rot" ist. Beachten Sie die Art der Nichtübereinstimmung:

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

aber,

Arrow :: * -> * -> *
94
Dietrich Epp

Mein Stil kann durch mein Telefon beengt sein, aber hier geht.

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

kann kein Functor sein. Wenn es so wäre, hätten wir

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

und der Mond würde aus grünem Käse bestehen.

Inzwischen

newtype Dead x = Oops {oops :: Void}

ist ein functor

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

kann aber nicht anwendbar sein, sonst hätten wir

oops (pure ()) :: Void

und Grün wäre aus Mondkäse (was eigentlich passieren kann, aber erst später am Abend).

(Zusätzlicher Hinweis: Void, wie in Data.Void ist ein leerer Datentyp. Wenn Sie versuchen, undefined zu verwenden, um zu beweisen, dass es sich um ein Monoid handelt, werde ich unsafeCoerce verwenden, um zu beweisen, dass dies nicht der Fall ist.)

Freudig,

newtype Boo x = Boo {boo :: Bool}

ist in vielerlei Hinsicht anwendbar, z. B. wie Dijkstra es haben würde,

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

aber es kann keine Monade sein. Um zu sehen, warum nicht, beobachten Sie, dass die Rückkehr konstant sein muss Boo True oder Boo False und daher das

join . return == id

kann unmöglich halten.

Oh ja, ich hätte es fast vergessen

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

ist eine Monade. Roll deinen eigenen.

Flugzeug zu fangen ...

82
pigworker

Ich glaube, die anderen Antworten haben einige einfache und allgemeine Beispiele übersehen:

Ein Typkonstruktor, der ein Functor, aber kein Applicative ist. Ein einfaches Beispiel ist ein Paar:

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

Es gibt jedoch keine Möglichkeit, die Applicative -Instanz zu definieren, ohne r zusätzliche Einschränkungen aufzuerlegen. Insbesondere gibt es keine Möglichkeit, pure :: a -> (r, a) für ein beliebiges r zu definieren.

Ein Typkonstruktor, der ein Applicative, aber keine Monad ist. Ein bekanntes Beispiel ist ZipList . (Es ist ein newtype, der Listen umschließt und ihnen eine andere Applicative Instanz zur Verfügung stellt.)

fmap wird wie gewohnt definiert. Aber pure und <*> Sind definiert als

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

also erstellt pure eine unendliche Liste, indem der angegebene Wert wiederholt wird, und <*> komprimiert eine Liste von Funktionen mit einer Liste von Werten - gilt i - i - tes Element. (Die Standardeinstellung <*> Für [] Erzeugt alle möglichen Kombinationen der Anwendung der i - ten Funktion auf das j - te Element. ) Aber es gibt keinen vernünftigen Weg, wie man eine Monade definiert (siehe dieser Beitrag ).


Wie passen Pfeile in die Hierarchie von Funktor/Anwendung/Monade? Siehe Redewendungen sind nicht bekannt, Pfeile sind akribisch, Monaden sind promiskuitiv von Sam Lindley, Philip Wadler und Jeremy Yallop. MSFP 2008. (Sie nennen anwendbare Funktoren Redewendungen.) Die Zusammenfassung:

Wir gehen noch einmal auf den Zusammenhang zwischen drei Begriffen der Berechnung ein: Moggis Monaden, Hughes 'Pfeile und die Redewendungen von McBride und Paterson (auch als applikative Funktoren bezeichnet). Wir zeigen, dass Idiome Pfeilen entsprechen, die den Typ Isomorphismus A ~> B = 1 ~> (A -> B) erfüllen, und dass Monaden Pfeilen entsprechen, die den Typ Isomorphismus A ~> B = A -> (1 ~ erfüllen > B). Ferner sind Redewendungen in Pfeile und Pfeile in Monaden eingebettet.

68
Petr Pudlák

Ein gutes Beispiel für einen Typkonstruktor, der kein Funktor ist, ist Set: Sie können fmap :: (a -> b) -> f a -> f b nicht implementieren, da ohne eine zusätzliche Einschränkung Ord b du kannst nicht konstruieren f b.

20
Landei

Ich möchte einen systematischeren Ansatz für die Beantwortung dieser Frage vorschlagen und auch Beispiele zeigen, bei denen keine speziellen Tricks wie "bottom" -Werte oder unendliche Datentypen oder ähnliches verwendet werden.

Wann haben Typkonstruktoren keine Typklasseninstanzen?

Im Allgemeinen kann ein Typkonstruktor aus zwei Gründen keine Instanz einer bestimmten Typklasse haben:

  1. Die Typensignaturen der erforderlichen Methoden aus der Typklasse können nicht implementiert werden.
  2. Kann die Typensignaturen implementieren, aber nicht die erforderlichen Gesetze erfüllen.

Beispiele der ersten Art sind einfacher als solche der zweiten Art, da wir für die erste Art lediglich prüfen müssen, ob eine Funktion mit einer bestimmten Typensignatur implementiert werden kann, während wir für die zweite Art nachweisen müssen, dass keine Implementierung erfolgt könnte möglicherweise die Gesetze befriedigen.

Spezifische Beispiele

  • Ein Typkonstruktor, der keine Funktionsinstanz haben kann , da der Typ nicht implementiert werden kann:

    data F a = F (a -> Int)
    

Dies ist ein Contrafunctor, kein Functor, da der Typparameter a in einer kontravarianten Position verwendet wird. Es ist unmöglich, eine Funktion mit der Typensignatur (a -> b) -> F a -> F b Zu implementieren.

  • Ein Typkonstruktor, der kein rechtmäßiger Funktor ist , obwohl die Typensignatur von fmap implementiert werden kann:

    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!
    

Der merkwürdige Aspekt dieses Beispiels ist, dass wir canfmap vom richtigen Typ implementieren, obwohl F unmöglich ein Funktor sein kann, weil er a in einer kontravarianten Position. Die oben gezeigte Implementierung von fmap ist also irreführend - obwohl sie die richtige Typensignatur aufweist (ich glaube, dies ist die einzig mögliche Implementierung dieser Typensignatur), sind die Funktionsgesetze nicht erfüllt (dies erfordert einige einfache Berechnungen) überprüfen).

Tatsächlich ist F nur ein Profunktor - es ist weder ein Funktor noch ein Contrafunktor.

  • Ein gesetzlicher Funktor, der nicht anwendbar ist , weil die Typensignatur von pure nicht implementiert werden kann: Nehmen Sie die Writer-Monade (a, w) und entfernen Sie die Bedingung, dass w ein Monoid sein soll. Es ist dann unmöglich, einen Wert vom Typ (a, w) Aus a zu konstruieren.

  • Ein nicht anwendbarer Funktor , da die Typensignatur von <*> Nicht implementiert werden kann: data F a = Either (Int -> a) (String -> a).

  • Ein nicht legal anwendbarer Funktor , obwohl die Typklassenmethoden implementiert werden können:

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

Der Typkonstruktor P ist ein Funktor, da er a nur in kovarianten Positionen verwendet.

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

Die einzig mögliche Implementierung der Typensignatur von <*> Ist eine Funktion, die immer Nothing zurückgibt:

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

Diese Implementierung erfüllt jedoch nicht das Identitätsgesetz für anwendbare Funktoren.

  • Ein Funktor, der Applicative ist, aber kein Monad, da die Typensignatur von bind nicht sein kann umgesetzt.

Solche Beispiele kenne ich nicht!

  • Ein Funktor, der Applicative, aber kein Monad ist , weil Gesetze nicht erfüllt werden können, obwohl die Typensignatur von bind kann implementiert werden.

Dieses Beispiel hat einige Diskussionen ausgelöst, daher kann man mit Sicherheit sagen, dass es nicht einfach ist, dieses Beispiel als richtig zu beweisen. Mehrere Personen haben dies jedoch unabhängig voneinander mit unterschiedlichen Methoden überprüft. Weitere Informationen finden Sie unter Ist `data PoE a = Empty | Pair a a` a Monade? .

 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

Es ist etwas umständlich zu beweisen, dass es keine rechtmäßige Monad Instanz gibt. Der Grund für das nicht-monadische Verhalten ist, dass es keinen natürlichen Weg gibt, bind zu implementieren, wenn eine Funktion f :: a -> B bNothing oder Just für unterschiedliche Werte zurückgeben könnte von a.

Es ist vielleicht klarer, Maybe (a, a, a) zu betrachten, was ebenfalls keine Monade ist, und zu versuchen, join dafür zu implementieren. Man wird feststellen, dass es keinen intuitiv vernünftigen Weg gibt, join zu implementieren.

 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.

In den durch ??? Angegebenen Fällen scheint es klar zu sein, dass wir aus sechs verschiedenen Werten des Typs aJust (z1, z2, z3) nicht auf vernünftige und symmetrische Weise erzeugen können. Wir könnten sicherlich eine beliebige Untermenge dieser sechs Werte wählen - zum Beispiel immer die erste nicht leere Maybe nehmen -, aber dies würde die Gesetze der Monade nicht erfüllen. Die Rückgabe von Nothing entspricht ebenfalls nicht den Gesetzen.

  • Eine baumartige Datenstruktur, die keine Monade ist, obwohl sie für bind assoziativ ist, aber die Identitätsgesetze nicht erfüllt.

Die übliche baumartige Monade (oder "ein Baum mit funktorförmigen Zweigen") wird definiert als

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

Dies ist eine freie Monade über den Funktor f. Die Form der Daten ist ein Baum, bei dem jeder Verzweigungspunkt eine "Fülle" von Teilbäumen ist. Der Standard-Binärbaum würde mit type f a = (a, a) erhalten.

Wenn wir diese Datenstruktur ändern, indem wir auch die Blätter in der Form des Funktors f erstellen, erhalten wir das, was ich als "Semimonad" bezeichne - es hat bind, das die Natur- und Assoziativitätsgesetze erfüllt , aber seine pure Methode versagt eines der Identitätsgesetze. "Semimonaden sind Halbgruppen in der Kategorie der Endofunktoren, was ist das Problem?" Dies ist die Typklasse Bind.

Der Einfachheit halber definiere ich die Methode join anstelle von 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)

Die Zweigtransplantation ist Standard, aber die Blatttransplantation ist nicht Standard und erzeugt eine Branch. Dies ist kein Problem für das Assoziativitätsgesetz, sondern bricht eines der Identitätsgesetze.

Wann haben Polynomtypen Monadeninstanzen?

Keiner der Funktoren Maybe (a, a) und Maybe (a, a, a) kann eine zulässige Monad -Instanz zugewiesen werden, obwohl es sich offensichtlich um Applicative handelt.

Diese Funktoren haben keine Tricks - keine Void oder bottom, keine knifflige Faulheit/Strenge, keine unendlichen Strukturen und keine Typklassenbeschränkungen. Die Instanz Applicative ist vollständig Standard. Die Funktionen return und bind können für diese Funktoren implementiert werden, erfüllen jedoch nicht die Gesetze der Monade. Mit anderen Worten, diese Funktoren sind keine Monaden, da eine bestimmte Struktur fehlt (es ist jedoch nicht leicht zu verstehen, was genau fehlt). Zum Beispiel kann eine kleine Änderung des Funktors eine Monade ergeben: data Maybe a = Nothing | Just a Ist eine Monade. Ein anderer ähnlicher Funktor data P12 a = Either a (a, a) ist ebenfalls eine Monade.

Konstruktionen für polynomische Monaden

Im Allgemeinen gibt es hier einige Konstruktionen, die zulässige Monads aus Polynomtypen erzeugen. In all diesen Konstruktionen ist M eine Monade:

  1. type M a = Either c (w, a) wobei w ein beliebiges Monoid ist
  2. type M a = m (Either c (w, a)) wobei m eine Monade und w ein Monoid ist
  3. type M a = (m1 a, m2 a) wobei m1 und m2 beliebige Monaden sind
  4. type M a = Either a (m a) wobei m eine beliebige Monade ist

Die erste Konstruktion ist WriterT w (Either c), die zweite Konstruktion ist WriterT w (EitherT c m). Die dritte Konstruktion ist ein komponentenbezogenes Produkt von Monaden: pure @M Ist definiert als das komponentenbezogene Produkt von pure @m1 Und pure @m2, Und join @M Ist definiert durch Weglassen produktübergreifender Daten (z. B. wird m1 (m1 a, m2 a) auf m1 (m1 a) abgebildet, indem der zweite Teil des Tupels weggelassen wird):

 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))

Die vierte Konstruktion ist definiert als

 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

Ich habe überprüft, dass alle vier Konstruktionen rechtmäßige Monaden hervorbringen.

Ich Vermutung dass es keine anderen Konstruktionen für polynomische Monaden gibt. Beispielsweise wird der Funktor Maybe (Either (a, a) (a, a, a, a)) durch keine dieser Konstruktionen erhalten und ist daher nicht monadisch. Either (a, a) (a, a, a) ist jedoch monadisch, da es isomorph zum Produkt von drei Monaden a, a und Maybe a Ist. Außerdem ist Either (a,a) (a,a,a,a) monadisch, da es isomorph zum Produkt von a und Either a (a, a, a) ist.

Die vier oben gezeigten Konstruktionen ermöglichen es uns, eine beliebige Summe einer beliebigen Anzahl von Produkten einer beliebigen Anzahl von a zu erhalten, zum Beispiel Either (Either (a, a) (a, a, a, a)) (a, a, a, a, a)) und so weiter. Alle derartigen Typkonstruktoren haben (mindestens eine) Monad -Instanz.

Es bleibt natürlich abzuwarten, welche Anwendungsfälle für solche Monaden existieren könnten. Ein weiteres Problem ist, dass die über die Konstruktionen 1-4 abgeleiteten Monad -Instanzen im Allgemeinen nicht eindeutig sind. Beispielsweise kann dem Typkonstruktor type F a = Either a (a, a) auf zwei Arten eine Monad -Instanz zugewiesen werden: durch Konstruktion 4 unter Verwendung der Monade (a, a) Und durch Konstruktion 3 unter Verwendung des Typisomorphismus Either a (a, a) = (a, Maybe a). Auch hier ist das Auffinden von Anwendungsfällen für diese Implementierungen nicht sofort offensichtlich.

Es bleibt eine Frage - bei einem beliebigen polynomiellen Datentyp, wie zu erkennen ist, ob es eine Monad -Instanz gibt. Ich weiß nicht, wie ich beweisen soll, dass es keine anderen Konstruktionen für polynomische Monaden gibt. Ich glaube, es gibt bisher keine Theorie, um diese Frage zu beantworten.

7
winitzki