Bewerber komponieren, Monaden nicht

110

Bewerber komponieren, Monaden nicht.

Was bedeutet die obige Aussage? Und wann ist einer dem anderen vorzuziehen?

fehlender Faktor
quelle
5
Woher hast du diese Aussage? Es kann hilfreich sein, einen Kontext zu sehen.
Fuz
@FUZxxl: Ich habe es wiederholt von vielen verschiedenen Leuten gehört, kürzlich von debasishg auf Twitter.
fehlender Faktor
3
@stephen tetley: Beachten Sie, dass viele solcher Applicatives tatsächlich eine ganze Familie von Monads sind, nämlich eine für jede mögliche "Form" der Struktur. ZipListist kein a Monad, aber ZipLists von fester Länge sind. Readerist ein praktischer Sonderfall (oder allgemein?), bei dem die Größe der "Struktur" als Kardinalität des Umgebungstyps festgelegt wird.
CA McCann
3
@CAMcCann Alle diese flotten Anwendungen (unabhängig davon, ob sie abgeschnitten oder aufgefüllt sind) beschränken sich auf Monaden, wenn Sie die Form so festlegen, dass sie einer ReaderMonade bis hin zum Isomorphismus entspricht. Sobald Sie die Form eines Containers festgelegt haben, wird eine Funktion effektiv aus Positionen codiert, z. B. eine Notiz. Peter Hancock nennt solche Funktoren "Naperian", da sie den Gesetzen der Logarithmen gehorchen.
Schweinearbeiter
4
@stephen tetley: Andere Beispiele sind das Konstant-Monoid-Applikativ (das eine Zusammensetzung von Monaden, aber keine Monade ist) und das Einheitsverzögerungs-Applikativ (das besser keine Verbindung zulassen sollte).
Schweinearbeiter

Antworten:

115

Wenn wir die Typen vergleichen

(<*>) :: Applicative a => a (s -> t) -> a s -> a t
(>>=) :: Monad m =>       m s -> (s -> m t) -> m t

Wir bekommen einen Hinweis darauf, was die beiden Konzepte trennt. Das (s -> m t)in der Art von (>>=)zeigt, dass ein Wert in sdas Verhalten einer Berechnung in bestimmen kann m t. Monaden ermöglichen Interferenzen zwischen der Wert- und der Berechnungsebene. Der (<*>)Operator lässt keine derartigen Störungen zu: Die Funktions- und Argumentberechnungen hängen nicht von Werten ab. Das beißt wirklich. Vergleichen Sie

miffy :: Monad m => m Bool -> m x -> m x -> m x
miffy mb mt mf = do
  b <- mb
  if b then mt else mf

Dabei wird das Ergebnis eines Effekts verwendet, um zwischen zwei Berechnungen zu entscheiden (z. B. Raketen abschießen und Waffenstillstand unterzeichnen)

iffy :: Applicative a => a Bool -> a x -> a x -> a x
iffy ab at af = pure cond <*> ab <*> at <*> af where
  cond b t f = if b then t else f

Dabei wird der Wert von verwendet ab, um zwischen den Werten zweier Berechnungen atund zu wählenaf nachdem beide ausgeführt wurden, möglicherweise mit tragischer Wirkung.

Die monadische Version beruht im Wesentlichen auf der zusätzlichen (>>=)Fähigkeit, eine Berechnung aus einem Wert auszuwählen, und das kann wichtig sein. Die Unterstützung dieser Kraft macht es jedoch schwierig, Monaden zu komponieren. Wenn wir versuchen, 'Doppelbindung' zu erstellen

(>>>>==) :: (Monad m, Monad n) => m (n s) -> (s -> m (n t)) -> m (n t)
mns >>>>== f = mns >>-{-m-} \ ns -> let nmnt = ns >>= (return . f) in ???

wir kommen so weit, aber jetzt sind unsere Schichten alle durcheinander. Wir haben eine n (m (n t)), also müssen wir das Äußere loswerden n. Wie Alexandre C sagt, können wir das tun, wenn wir einen geeigneten haben

swap :: n (m t) -> m (n t)

das Innere nund joines zum anderen zu permutierenn .

Die schwächere Doppelanwendung ist viel einfacher zu definieren

(<<**>>) :: (Applicative a, Applicative b) => a (b (s -> t)) -> a (b s) -> a (b t)
abf <<**>> abs = pure (<*>) <*> abf <*> abs

weil es keine Interferenz zwischen den Schichten gibt.

Dementsprechend ist es gut zu erkennen, wann Sie wirklich die zusätzliche Leistung von Monads benötigen und wann Sie mit der starren Rechenstruktur davonkommen können, die Applicativeunterstützt.

Beachten Sie übrigens, dass das Komponieren von Monaden zwar schwierig ist, aber möglicherweise mehr als nötig ist. Der Typ m (n v)gibt an m, mit n-Effekten zu rechnen und dann mit -Effekten zu einem -Wert zu rechnen v, wobei die m-Effekte enden, bevor die n-Effekte beginnen (daher ist dies erforderlich swap). Wenn Sie nur m-Effekte mit n-Effekten verschachteln möchten , ist die Komposition vielleicht zu viel verlangt!

Schweinearbeiter
quelle
3
Für das zweifelhafte Beispiel geben Sie an, dass es "den Wert von ab verwendet, um zwischen den Werten von zwei Berechnungen bei und af zu wählen, nachdem beide ausgeführt wurden, möglicherweise mit tragischer Wirkung". Schützt Sie die Faulheit von Haskell nicht davor? Wenn ich list = (\ btf -> wenn b dann t sonst f): [] habe und dann die Anweisung ausführe: list <*> pure True <*> pure "hallo" <*> pure (Fehler "bad"). ... Ich bekomme "Hallo" und der Fehler tritt nie auf. Dieser Code ist bei weitem nicht so sicher oder kontrolliert wie eine Monade, aber der Beitrag scheint darauf hinzudeuten, dass Applikative eine strikte Bewertung verursachen. Insgesamt ein toller Beitrag! Vielen Dank!
Shj
7
Sie erhalten immer noch die Auswirkungen von beiden, aber pure (Fehler "schlecht") hat keine. Wenn Sie andererseits versuchen, iffy (pure True) (pure "hallo") (Fehler "bad"), erhalten Sie einen Fehler, den miffy vermeidet. Wenn Sie außerdem etwas wie iffy (pure True) (pure 0) [1,2] versuchen, erhalten Sie [0,0] anstelle von [0]. Bewerber haben eine Art Strenge, indem sie feste Abfolgen von Berechnungen erstellen, aber die Werte, die sich aus diesen Berechnungen ergeben, werden, wie Sie beobachten, immer noch träge kombiniert.
Schweinearbeiter
Stimmt es, dass für jede Monade mund nSie immer einen Monadentransformator schreiben mtund mit n (m t)verwenden können mt n t? Sie können also immer Monaden komponieren, es ist nur komplizierter, mit Transformatoren?
Ron
4
Solche Transformatoren gibt es oft, aber meines Wissens gibt es keine kanonische Möglichkeit, sie zu erzeugen. Es gibt oft eine echte Wahl, wie verschachtelte Effekte aus den verschiedenen Monaden aufgelöst werden sollen. Das klassische Beispiel sind Ausnahmen und Zustand. Sollte sich ein Ausnahme-Rollback-Status ändern oder nicht? Beide Entscheidungen haben ihren Platz. Trotzdem gibt es eine "freie Monade", die "willkürliche Verschachtelung" ausdrückt. data Free f x = Ret x | Do (f (Free f x))dann data (:+:) f g x = Inl (f x) | Tnr (g x)und überlegen Free (m :+: n). Dies verzögert die Auswahl der Interleavings.
Schweinearbeiter
@pigworker Bezüglich der faulen / strengen Debatte. Ich denke, dass Sie mit Applikativen den Effekt nicht innerhalb der Berechnung steuern können , aber die Effektschicht kann sehr gut entscheiden, spätere Werte nicht auszuwerten. Für (anwendbare) Parser bedeutet dies, dass nachfolgende Parser nicht ausgewertet / auf die Eingabe angewendet werden, wenn der Parser vorzeitig ausfällt. Für MaybeDies bedeutet , dass ein frühes Nothingwird die Bewertung der Unterdrückung aeiner späteren / Folge Just a. Ist das richtig?
Ziggystar
75

Bewerber komponieren, Monaden nicht.

Monaden tun compose, aber das Ergebnis könnte nicht eine Monade sein. Im Gegensatz dazu ist die Zusammensetzung von zwei Applikativen notwendigerweise ein Applikativ. Ich vermute, die Absicht der ursprünglichen Aussage war, dass "Anwendbarkeit komponiert, Monadität nicht". Umformuliert: "Wird Applicativeunter Komposition geschlossen und Monadnicht."

Conal
quelle
24
Zusätzlich setzen sich zwei beliebige Applikative auf vollständig mechanische Weise zusammen, während die durch die Zusammensetzung von zwei Monaden gebildete Monade für diese Zusammensetzung spezifisch ist.
Apocalisp
12
Darüber hinaus komponieren Monaden auf andere Weise. Das Produkt zweier Monaden ist eine Monade. Nur die Nebenprodukte benötigen ein Verteilungsgesetz.
Edward KMETT
Mit @Apocalisp, einschließlich Kommentar, ist dies die beste und prägnanteste Antwort.
Paul Draper
39

Wenn Sie Anwendungen haben A1und A2dann den Typdata A3 a = A3 (A1 (A2 a)) auch anwendbar (Sie können eine solche Instanz generisch schreiben).

Wenn Sie dagegen Monaden haben M1und M2der Typ data M3 a = M3 (M1 (M2 a))dann nicht unbedingt eine Monade ist (es gibt keine sinnvolle generische Implementierung für >>=oderjoin für die Komposition).

Ein Beispiel kann der Typ sein [Int -> a](hier erstellen wir einen Typkonstruktor, []mit dem (->) Intbeide Monaden sind). Sie können leicht schreiben

app :: [Int -> (a -> b)] -> [Int -> a] -> [Int -> b]
app f x = (<*>) <$> f <*> x

Und das verallgemeinert sich auf jede Anwendung:

app :: (Applicative f, Applicative f1) => f (f1 (a -> b)) -> f (f1 a) -> f (f1 b)

Aber es gibt keine vernünftige Definition von

join :: [Int -> [Int -> a]] -> [Int -> a]

Wenn Sie davon nicht überzeugt sind, betrachten Sie diesen Ausdruck:

join [\x -> replicate x (const ())]

Die Länge der zurückgegebenen Liste muss in Stein gemeißelt werden, bevor jemals eine Ganzzahl angegeben wird. Die richtige Länge hängt jedoch von der bereitgestellten Ganzzahl ab. Daher joinkann für diesen Typ keine korrekte Funktion existieren.

Rotsor
quelle
1
... also Monaden vermeiden, wenn eine Funktion ausreicht?
Andrew Cooke
2
@ Andrew, wenn Sie Funktor gemeint haben, dann sind Funktoren einfacher und sollten verwendet werden, wenn dies ausreicht. Beachten Sie, dass dies nicht immer der Fall ist. Zum Beispiel IOohne Monadwäre ein sehr schwer zu programmieren. :)
Rotsor
17

Leider ist unser eigentliches Ziel, die Zusammensetzung der Monaden, etwas schwieriger. Tatsächlich können wir tatsächlich beweisen, dass es in gewissem Sinne keine Möglichkeit gibt, eine Verknüpfungsfunktion mit dem obigen Typ nur mit den Operationen der beiden Monaden zu konstruieren (eine Übersicht über den Beweis finden Sie im Anhang). Daraus folgt, dass wir nur hoffen können, eine Komposition zu bilden, wenn es einige zusätzliche Konstruktionen gibt, die die beiden Komponenten verbinden.

Monaden komponieren, http://web.cecs.pdx.edu/~mpj/pubs/RR-1004.pdf

Landei
quelle
4
Tl; dr für ungeduldige Leser: Sie können Monaden komponieren, wenn (f?) Sie eine natürliche Transformation liefern könnenswap : N M a -> M N a
Alexandre C.
@Alexandre C.: Nur "wenn", vermute ich. Nicht alle Monadentransformatoren werden durch direkte Funktorkomposition beschrieben. Zum Beispiel ContT r m aist weder m (Cont r a)noch Cont r (m a)und StateT s m aist ungefähr Reader s (m (Writer s a)).
CA McCann
@CA McCann: Ich kann nicht von (M-Monade, N-Monade, MN-Monade, NM-Monade) nach (es gibt einen Tausch: MN -> NM natürlich) kommen. Bleiben wir also vorerst bei "wenn" (vielleicht steht die Antwort in der Zeitung, ich muss gestehen, dass ich sie schnell nachgeschlagen habe)
Alexandre C.
1
@Alexandre C.: Es reicht vielleicht ohnehin nicht aus, nur anzugeben, dass es sich bei den Kompositionen um Monaden handelt - Sie müssen auch eine Möglichkeit finden, die beiden Teile mit dem Ganzen in Beziehung zu setzen. Die Existenz von swapimpliziert, dass die Komposition die beiden irgendwie "kooperieren" lässt. Beachten Sie auch, dass sequencedies für einige Monaden ein Sonderfall des "Austauschs" ist. So ist es flipeigentlich.
CA McCann
7
Um zu schreiben swap :: N (M x) -> M (N x), sieht es für mich so aus, als könnten Sie returns(in geeigneter Weise fmapped) eine Mvorne und eine Nhinten einfügen und N (M x) -> M (N (M (N x)))dann die joindes Komposits verwenden, um Ihre zu erhalten M (N x).
Schweinearbeiter
7

Die Verteilungsgesetzlösung l: MN -> NM reicht aus

Monadizität von NM zu garantieren. Um dies zu sehen, benötigen Sie eine Einheit und ein Mult. Ich werde mich auf das Mult konzentrieren (die Einheit ist unit_N unitM)

NMNM - l -> NNMM - mult_N mult_M -> NM

Das geht nicht garantiert , dass MN eine Monade ist.

Die entscheidende Beobachtung kommt jedoch ins Spiel, wenn Sie verteilungsrechtliche Lösungen haben

l1 : ML -> LM
l2 : NL -> LN
l3 : NM -> MN

Somit sind LM, LN und MN Monaden. Es stellt sich die Frage, ob LMN eine Monade ist (entweder von

(MN) L -> L (MN) oder durch N (LM) -> (LM) N.

Wir haben genug Struktur, um diese Karten zu erstellen. Wie Eugenia Cheng jedoch bemerkt , benötigen wir eine hexagonale Bedingung (die einer Darstellung der Yang-Baxter-Gleichung entspricht), um die Monadizität beider Konstruktionen zu gewährleisten. Tatsächlich fallen mit der hexagonalen Bedingung die zwei verschiedenen Monaden zusammen.

user278559
quelle
9
Dies ist wahrscheinlich eine großartige Antwort, aber sie ging mir weit über den Kopf.
Dan Burton
1
Dies liegt daran, dass es sich bei Verwendung des Begriffs "Anwendbar" und "Haskell-Tag" um eine Frage zu "Haskell" handelt, die jedoch in einer anderen Notation beantwortet wird.
Codeshot