2016-02-14 3 views
8

Ich bin neu in der funktionalen Programmierung (kommt von Javascript), und ich habe eine harte Zeit, den Unterschied zwischen den beiden zu sagen, die auch mit meinem Verständnis von Funktoren vs. Monaden irrte.Unterschied in der Fähigkeit zwischen fmap und bind?

Functor:

class Functor f where 
    fmap :: (a -> b) -> f a -> f b 

Monad (vereinfacht):

class Monad m where 
    (>>=) :: m a -> (a -> m b) -> m b 
  • fmap nimmt eine Funktion und eine Funktors und gibt eine Funktors.
  • >>= nimmt eine Funktion und eine Monade und gibt eine Monade zurück.

Der Unterschied zwischen den beiden ist in den Funktionsparameter:

  • fmap - (a -> b)
  • >>= - (a -> m b)

>>= nimmt ein Funktionsparameter, der eine monadisch zurückgibt. Ich weiß, dass das wichtig ist, aber ich habe Schwierigkeiten zu sehen, wie dieses eine kleine Ding Monaden viel mächtiger macht als Funktoren. Kann jemand das erklären?

+4

Dies ist einfacher mit der umgedrehten Version von '(>> =)', ['(= <<)'] (https://stackoverflow.com/questions/34545818/is-monad-bind-operator- Näher-Funktion-Zusammensetzung-Verkettung-oder-Funktionen/34561605 # 34561605). Mit '(g <$>) :: f a -> f b 'hat die Funktion' g :: a -> b' keinen Einfluss auf das 'f'" Umbrechen "- ändert es nicht. Mit '(k = <<) :: m a -> mb' erzeugt die Funktion' k :: a -> mb' selbst * das neue 'm'" Wrapping ", damit es sich ändern kann. –

+0

@WillNess Ich kann das" verstehen ", aber ich Ich glaube, das eigentliche Problem, das ich habe, ist, dass ich nicht sehen kann, was '' '' 'tun kann' 'fmap' kann nicht. In meinem Kopf sind sie gleichwertig, weil ich es nicht gesehen habe ein Beispiel, das zeigt, dass fmap nicht ausreicht – m0meni

+4

gehen Sie mit Listen, versuchen Sie, einige Elemente aus einer Liste herauszufiltern, mit 'map', das können Sie nicht, aber mit' concatMap' können Sie: 'map (\ x- > x + 1) [1,2,3] 'vs' concatMap (\ x-> [x, x + 1 | gerade x]) [1,2,3]) '. –

Antwort

13

Nun, (<$>) ein Alias ​​für fmap und ist die gleiche wie (>>=) mit den Argumenten vertauscht:

(<$>) :: (x -> y) -> b x -> b y 
(=<<) :: (x -> b y) -> b x -> b y 

Der Unterschied ist jetzt ziemlich klar: mit der bind Funktion verwenden wir eine Funktion, die zurückgibt ein b y eher als ein y. Was für einen Unterschied macht das?

Betrachten Sie dieses kleine Beispiel:

foo <$> Just 3 

Beachten Sie, dass (<$>)foo-3 gelten wird, und das Ergebnis in eine Just zurückstellen. Mit anderen Worten, das Ergebnis dieser Berechnung kann nichtNothing sein. Im Gegenteil:

bar =<< Just 3 

Diese Berechnung kann zurückkehren Nothing. (Zum Beispiel wird bar x = Nothing es tun.)

Wir können mit der Liste Monade eine ähnliche Sache tun:

foo <$> [Red, Yellow, Blue] -- Result is guaranteed to be a 3-element list. 
bar =<< [Red, Yellow, Blue] -- Result can be ANY size. 

Kurz gesagt, mit (<$>) (dh fmap), der „Struktur“ des Ergebnisses ist immer identisch mit der Eingabe.Aber mit (d. H. (>>=)) kann sich die Struktur des Ergebnisses ändern. Dies ermöglicht eine bedingte Ausführung, Reaktion auf Eingaben und eine ganze Reihe anderer Dinge.

+4

nur für die Vollständigkeit, Applicative kann auch 'Nothing' zurückgeben:' Nothing <*> Nur 3'. Der Unterschied besteht darin, dass die "Verrohrung" (d. H. Die Berechnungsstruktur) * fixiert * ist, wenn die Berechnung zusammengesetzt ist, * bevor * sie * "läuft". Aber bei Monaden kann sich die Verrohrung abhängig von den produzierten Werten * ändern, während * sie "läuft". (Im Fall von IO wird "3" beispielsweise als Eingabe des Benutzers empfangen). - Das * list * Beispiel ist esp. gut hier: '(foo <$>)' behält die Struktur (Listenlänge); '([baz, quux] <*>)' ändert die Struktur * vorhersagbar * (länge-6-Liste erstellen); mit Monad sind alle Wetten aus. –

8

Kurze Antwort ist, dass, wenn Sie in m a in einer Weise, die sinnvoll ist, dann ist es ein Monad. Dies ist für alle Monaden möglich, aber nicht unbedingt für Funktoren.

Ich denke, die meisten verwirrend ist, dass alle gemeinsamen Beispiele für Funktoren (zum Beispiel List, Maybe, IO) sind auch Monaden. Wir brauchen ein Beispiel für etwas, das ein Functor ist, aber keine Monade.

Ich werde ein Beispiel aus einem hypothetischen Kalenderprogramm verwenden. Der folgende Code definiert einen Functor Event, der einige Daten speichert, die zu dem Ereignis und der Uhrzeit gehören, zu der es auftritt.

import Data.Time.LocalTime 

data Event a = MkEvent LocalTime a 

instance Functor Event where 
    fmap f (MkEvent time a) = MkEvent time (f a) 

Die Event Objekt speichert die Zeit, die das Ereignis auftritt und einige zusätzliche Daten, die fmap Verwendung geändert werden kann. Schauen wir uns nun versuchen, eine Monade zu machen:

instance Monad Event where 
    (>>=) (MkEvent timeA a) f = let (MkEvent timeB b) = f a in 
           MkEvent <notSureWhatToPutHere> b 

wir, dass wir nicht finden können, weil Sie mit zwei LocalTime Objekte landen. timeA aus dem angegebenen Event und timeB aus dem Event ergeben sich durch das Ergebnis f a. Unser Event Typ ist so definiert, dass er nur einen LocalTime (time) hat, an dem er auftritt, und so ist es unmöglich, eine Monade zu machen, ohne zwei LocalTime s in eins zu verwandeln. (Es kann einen Fall geben, in dem dies Sinn machen könnte und du könntest dies in eine Monade verwandeln, wenn du wirklich willst).

+3

Ein Beispiel für einen klassischen/gemeinsamen Funktor, der keine Monade ist, ist 'newtype Const a b = Const a'. – dfeuer

+3

'pure x >> = f 'wird von einem Monadengesetz benötigt, um' f x' zu sein, aber 'pure :: b -> Const a b 'kann möglicherweise sein Argument nicht verwenden. – dfeuer

+1

@dfeuer Dies scheint [zu einfach, um einfach zu sein] (https://ncatlab.org/nlab/show/too+simple+to+be+simple). Ich finde auch keine Möglichkeit, eine Funktor-Instanz anders als 'fmap f (Const x) = Const x' – HEGX64

3

Angenommen, IO waren nur ein Functor, und kein Monad. Wie könnten wir zwei Aktionen abfolgen? Sprich, wie getChar :: IO Char und putChar :: Char -> IO().

Wir könnten versuchen, über getChar (eine Aktion, die, wenn ausgeführt, liest eine Char von Stdin) mit putChar.

fmap putChar getChar :: IO (IO()) 

Jetzt haben wir ein Programm, das, wenn es ausgeführt wird, eine Char von stdin liest und erzeugt ein Programm, das, wenn es ausgeführt wird, die Char nach stdout schreibt. Aber was wir eigentlich wollen, ist ein Programm, das, wenn es ausgeführt wird, eine Char von stdin liest und die Char auf stdout schreibt. Also brauchen wir eine „Abflachung“ (im IO Fall „sequencing“) Funktion mit Typ:

join :: IO (IO()) -> IO() 

Functor selbst diese Funktion nicht zur Verfügung stellen. Aber es ist eine Funktion der Monad, wo es die allgemeinere Art hat:

join :: Monad m => m (m a) -> m a 

Was bedeutet all dies mit >>= zu tun? Wie es passiert, ist monadischen binden nur eine Kombination aus fmap und join:

:t \m f -> join (fmap f m) 
(Monad m) => m a1 -> (a1 -> m a) -> m a 

Eine weitere Möglichkeit, den Unterschied zu sehen ist, dass fmap nie die Gesamtstruktur des abgebildeten Wert ändert, aber join (und damit >>= auch) kann das tun.

In Bezug auf die IO Aktionen, fmap wird nie Ursache Wichtige liest/schreibt oder andere Effekte. Aber join sequenziert das Lesen/Schreiben der inneren Aktion nach denen der äußeren Aktion.

Verwandte Themen