Was ist der Sinn der Karte in Haskell, wenn es eine fmap gibt?

97

Überall, wo ich es versucht habe map, fmaphat es auch funktioniert. Warum hatten die Schöpfer von Haskell das Bedürfnis nach einer mapFunktion? Könnte es nicht einfach das sein, was derzeit bekannt ist fmapund fmapaus der Sprache entfernt werden könnte?

Clark Gaebel
quelle
11
Ich denke, Sie fragen: "Was bringt fmap in Haskell?"
zw324
11
Ich weiß, worum es fmapgeht. Es dient zum Zuordnen einer Funktion zu einer Functor-Instanz. Ich frage mich über den Zweck der Spezialisierung auf map.
Clark Gaebel

Antworten:

95

Ich möchte eine Antwort geben, um auf Augustss 'Kommentar aufmerksam zu machen :

So passiert es eigentlich nicht. Was passierte war, dass der Kartentyp verallgemeinert wurde, um Functor in Haskell 1.3 abzudecken. Dh in Haskell 1.3 wurde fmap map genannt. Diese Änderung wurde dann in Haskell 1.4 rückgängig gemacht und fmap wurde eingeführt. Der Grund für diese Änderung war pädagogisch; Wenn Anfängern Haskell beigebracht wurde, erschwerte der sehr allgemeine Kartentyp das Verstehen von Fehlermeldungen. Meiner Meinung nach war dies nicht der richtige Weg, um das Problem zu lösen.

Haskell 98 wird von einigen Haskellern (einschließlich mir) als Rückschritt angesehen, da frühere Versionen eine abstraktere und konsistentere Bibliothek definiert haben. Naja.

luqui
quelle
16
Werden diese Rückschritte irgendwo gesammelt und dokumentiert? Es wäre interessant zu sehen, was sonst noch als Rückschritt angesehen wurde und ob es auch bessere Lösungen dafür gibt.
Davorak
3
Das map and fmapgibt es schon lange - es wurde im August 2006 wieder auf die Haskell-Prime-Mailingliste gesetzt - haskell.org/pipermail/haskell-prime/2006-August/thread.html . Als Kontrapunkt bevorzuge ich den Status Quo. Für mich scheint es wertvoll, dass es eine Untergruppe von Haskell gibt, die in etwa Miranda entspricht. In Großbritannien wurde Miranda als Unterrichtssprache für Mathematikstudenten verwendet, nicht nur für Informatikstudenten. Wenn diese Nische nicht bereits für eine nicht funktionierende Sprache (z. B. Mathematica) verloren ist, sehe ich Haskell nicht mit einer einheitlichen mapFüllung.
Stephen Tetley
35
Und ich möchte für alle, die es noch nicht wissen, weiter darauf hinweisen, dass es sich bei Augusts um Lennart Augustsson handelt, der praktisch schon vor der Existenz von Haskell Teil der Haskell-Community war, vgl. Eine Geschichte von Haskell , daher handelt es sich bei dem fraglichen Kommentar in keiner Weise um gebrauchtes Hörensagen!
CA McCann
3
Es gibt jetzt eine Nitpicks-Seite im Haskell-Wiki, auf der dieses Problem erwähnt wird.
Alexey
Es ist lustig, dass diese Entscheidung aus pädagogischen Gründen getroffen wurde, weil ich beim Lernen von Haskell ständig fmap mit flatmap verwechselt habe. Sie hätten eine n00b-Fokusgruppe zusammenstellen sollen. :)
Danny Andrews
27

Zitat aus der FunctorDokumentation unter https://wiki.haskell.org/Typeclassopedia#Functor

Sie fragen sich vielleicht, warum wir eine separate mapFunktion benötigen . Warum nicht einfach die aktuelle Nur-Listen- mapFunktion abschaffen fmapund map stattdessen in umbenennen ? Das ist eine gute Frage. Das übliche Argument ist, dass jemand, der gerade Haskell lernt, bei mapfalscher Verwendung lieber einen Fehler über Listen als über sieht Functor.

Andrei Bozantan
quelle
1
Das macht für mich sehr viel Sinn.
Hbobenicio
25

Sie sehen auf der Anwendungsseite gleich aus, sind aber natürlich unterschiedlich. Wenn Sie eine dieser beiden Funktionen anwenden, mapoderfmap auf eine Liste von Werten , werden dieselben Ergebnisse erzielt. Dies bedeutet jedoch nicht, dass sie für denselben Zweck bestimmt sind.

Führen Sie eine GHCI-Sitzung (Glasgow Haskell Compiler Interactive) aus, um Informationen zu diesen beiden Funktionen abzufragen. Schauen Sie sich dann deren Implementierungen an und Sie werden viele Unterschiede feststellen.

Karte

Fragen Sie GHCI nach Informationen zu ab map

Prelude> :info map
map :: (a -> b) -> [a] -> [b]   -- Defined in ‘GHC.Base’

und Sie werden sehen, dass es als eine Funktion höherer Ordnung definiert ist, die auf eine Liste von Werten eines beliebigen Typs anwendbar ist und eine Liste von Werten eines beliebigen Typs aergibt b. Obwohl polymorph (die aund bin der obigen Definition stehen für jeden Typ), soll die mapFunktion auf eine Liste von Werten angewendet werden, die nur ein möglicher Datentyp unter vielen anderen in Haskell ist. Die mapFunktion konnte nicht auf etwas angewendet werden, das keine Werteliste ist.

Wie Sie aus dem GHC.Base- Quellcode lesen können , ist die mapFunktion wie folgt implementiert

map _ []     = []
map f (x:xs) = f x : map f xs

Dabei wird der Mustervergleich verwendet, um den Kopf (den x) vom Ende (der xs) der Liste abzuziehen. Anschließend wird eine neue Liste erstellt, indem der :(cons) -Wertkonstruktor verwendet wird, um vorangestellt zu werden f x(lesen Sie ihn als "f auf x angewendet" ). zur Rekursion mapüber den Schwanz, bis die Liste leer ist. Es ist erwähnenswert, dass die Umsetzung dermap Funktion nicht von einer anderen Funktion sondern nur von sich selbst.

fmap

Versuchen Sie nun, Informationen zu abfragen, fmapund Sie werden etwas ganz anderes sehen.

Prelude> :info fmap
class Functor (f :: * -> *) where
  fmap :: (a -> b) -> f a -> f b
  ...
  -- Defined in ‘GHC.Base’

Diese Zeit fmapist als eine der Funktionen definiert, deren Implementierungen von den Datentypen bereitgestellt werden müssen, die zur FunctorTypklasse gehören möchten . Das bedeutet, dass es mehr als einen Datentyp geben kann, nicht nur den Datentyp "Liste der Werte" , der eine Implementierung für die fmapFunktion bereitstellen kann. Das fmapgilt für einen viel größeren Datensatz: die Funktoren in der Tat!

Wie Sie aus dem GHC.Base- Quellcode lesen können , ist eine mögliche Implementierung der fmapFunktion diejenige, die vom MaybeDatentyp bereitgestellt wird :

instance  Functor Maybe  where
  fmap _ Nothing       = Nothing
  fmap f (Just a)      = Just (f a)

und eine andere mögliche Implementierung ist die, die durch den 2-Tupel-Datentyp bereitgestellt wird

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

und eine andere mögliche Implementierung ist die, die vom Listendatentyp bereitgestellt wird (natürlich!):

instance  Functor []  where
  fmap f xs = map f xs

das hängt von der mapFunktion ab.

Fazit

Die mapFunktion kann nur auf eine Liste von Werten angewendet werden (wobei Werte von einem beliebigen Typ sind), während die fmapFunktion auf viel mehr Datentypen angewendet werden kann: alle, die zur Funktorklasse gehören (z. B. Maybes, Tupel, Listen usw.). ). Da der Datentyp "Werteliste" auch ein Funktor ist (weil er eine Implementierung dafür bereitstellt), fmapkann er angewendet werden und liefert auch das gleiche Ergebnis wie map.

map  (+3) [1..5]
fmap (+3) (Just 15)
fmap (+3) (5, 7)
Paolo Angioletti
quelle