Haskell: Wie wird <*> ausgesprochen? [geschlossen]

109

Wie spricht man diese Funktionen in der Anwendungsklasse aus?

(<*>) :: f (a -> b) -> f a -> f b
(*>)  :: f a -> f b -> f b
(<*)  :: f a -> f b -> f a

(Das heißt, wenn sie keine Operatoren wären, wie könnten sie genannt werden?)

Als Randnotiz, wenn Sie purein etwas umbenennen könnten, das für Nicht-Mathematiker freundlicher ist, wie würden Sie es nennen?

J Cooper
quelle
6
@J Cooper ... würdest du hören können, wie wir es aussprechen? :) Vielleicht möchten Sie auf meta.stackoverflow.com eine Anfrage für eine Sprachaufnahme- und Wiedergabefunktion stellen :).
Kiril
8
Es wird ausgesprochen "Gute Trauer, ihnen gingen wirklich die Betreiber aus, nicht wahr?" Auch ein guter Name für purekönnte sein makeApplicative.
Chuck
@Lirik, Nun, ich denke mit aussprechen meine ich "whaddya call this thing" :) @Chuck, poste deinen pureVorschlag als Antwort und ich werde dich positiv bewerten
J Cooper
6
(<*>) ist die Control.Applicative-Version von Control.Monads "ap", daher ist "ap" wahrscheinlich der am besten geeignete Name.
Edward KMETT
11
Ich würde es einen Zyklopen nennen, aber das bin nur ich.
RCIX

Antworten:

245

Entschuldigung, ich kenne meine Mathematik nicht wirklich, daher bin ich gespannt, wie die Funktionen in der anwendbaren Typklasse ausgesprochen werden

Ihre Mathematik zu kennen oder nicht, ist hier weitgehend irrelevant, denke ich. Wie Sie wahrscheinlich wissen, leiht sich Haskell ein paar Begriffe aus verschiedenen Bereichen der abstrakten Mathematik, insbesondere der Kategorietheorie , aus denen wir Funktoren und Monaden erhalten. Die Verwendung dieser Begriffe in Haskell weicht etwas von den formalen mathematischen Definitionen ab, aber sie sind normalerweise nahe genug, um ohnehin gute beschreibende Begriffe zu sein.

Die ApplicativeTypklasse liegt irgendwo zwischen Functorund Monad, daher würde man erwarten, dass sie eine ähnliche mathematische Basis hat. Die Dokumentation zum Control.ApplicativeModul beginnt mit:

Dieses Modul beschreibt eine Struktur zwischen einem Funktor und einer Monade: Es bietet reine Ausdrücke und Sequenzierung, aber keine Bindung. (Technisch gesehen ein starker laxer monoidaler Funktor.)

Hmm.

class (Functor f) => StrongLaxMonoidalFunctor f where
    . . .

Nicht ganz so eingängig wie Monad, denke ich.

Im Grunde Applicativeläuft alles darauf hinaus, dass es keinem mathematisch besonders interessanten Konzept entspricht. Es liegen also keine vorgefertigten Begriffe herum, die die Art und Weise erfassen, wie es in Haskell verwendet wird. Legen Sie also die Mathematik vorerst beiseite.


Wenn wir wissen wollen, wie wir es nennen sollen, kann (<*>)es hilfreich sein zu wissen, was es im Grunde bedeutet.

Also, was ist überhaupt los Applicativeund warum nennen wir es so?

Was Applicativein der Praxis bedeutet, ist eine Möglichkeit, beliebige Funktionen in a zu heben Functor. Betrachten Sie die Kombination von Maybe(wohl der einfachste nicht triviale Functor) und Bool(ebenfalls der einfachste nicht triviale Datentyp).

maybeNot :: Maybe Bool -> Maybe Bool
maybeNot = fmap not

Mit dieser Funktion fmapkönnen wir notvon der Arbeit Boolzur Arbeit übergehen Maybe Bool. Aber was ist, wenn wir heben wollen (&&)?

maybeAnd' :: Maybe Bool -> Maybe (Bool -> Bool)
maybeAnd' = fmap (&&)

Nun, das wollen wir überhaupt nicht ! In der Tat ist es so ziemlich nutzlos. Wir können versuchen , schlau zu sein und heimlich eine andere Boolin Maybedurch die Hintertür ...

maybeAnd'' :: Maybe Bool -> Bool -> Maybe Bool
maybeAnd'' x y = fmap ($ y) (fmap (&&) x)

... aber das ist nicht gut. Zum einen ist es falsch. Zum anderen ist es hässlich . Wir könnten es weiter versuchen, aber es stellt sich heraus, dass es keine Möglichkeit gibt, eine Funktion mehrerer Argumente aufzuheben, um an einer beliebigen zu arbeitenFunctor . Nervig!

Auf der anderen Seite konnten wir es einfach tun , wenn wir gebraucht Maybe‚s MonadBeispiel:

maybeAnd :: Maybe Bool -> Maybe Bool -> Maybe Bool
maybeAnd x y = do x' <- x
                  y' <- y
                  return (x' && y')

Das ist eine Menge Aufwand, nur um eine einfache Funktion zu übersetzen - weshalb Control.Monadeine Funktion bereitgestellt wird, um dies automatisch zu tun liftM2. Die 2 in ihrem Namen bezieht sich auf die Tatsache, dass sie mit Funktionen von genau zwei Argumenten arbeitet; Ähnliche Funktionen gibt es für 3, 4 und 5 Argumentfunktionen. Diese Funktionen sind besser , aber nicht perfekt, und die Angabe der Anzahl der Argumente ist hässlich und ungeschickt.

Das bringt uns zu dem Artikel, in dem die Typklasse "Anwendbar" eingeführt wurde . Darin machen die Autoren im Wesentlichen zwei Beobachtungen:

  • Das Aufheben von Funktionen mit mehreren Argumenten in eine Functorist eine sehr natürliche Sache
  • Dies erfordert nicht die volle Leistungsfähigkeit von a Monad

Die Anwendung mit normalen Funktionen wird durch einfaches Nebeneinander von Begriffen geschrieben. Um die "angehobene Anwendung" so einfach und natürlich wie möglich zu gestalten, werden Infix-Operatoren vorgestellt, die für die Anwendung stehen, in dieFunctor und eine Typklasse gehoben werden , um das bereitzustellen, was dafür benötigt wird .

All dies bringt uns zu folgendem Punkt: (<*>)Stellt einfach die Funktionsanwendung dar - warum sollte man sie also anders aussprechen als den Leerzeichen-Operator "Nebeneinander"?

Aber wenn das nicht sehr befriedigend ist, können wir beobachten, dass das Control.MonadModul auch eine Funktion bietet, die dasselbe für Monaden tut:

ap :: (Monad m) => m (a -> b) -> m a -> m b

Wo apist natürlich die Abkürzung für "bewerben". Da jedes sein Monadkann Applicativeund apnur die Teilmenge der in letzterem vorhandenen Merkmale benötigt, können wir vielleicht sagen, dass es aufgerufen werden sollte , wenn (<*>)es kein Operator wäre ap.


Wir können uns den Dingen auch aus der anderen Richtung nähern. Der FunctorHebevorgang wird aufgerufen, fmapweil er eine Verallgemeinerung des mapVorgangs auf Listen darstellt. Welche Art von Funktion auf Listen würde funktionieren (<*>)? Da ist wasap gibt natürlich das, auf Listen steht, aber das allein ist nicht besonders nützlich.

In der Tat gibt es eine vielleicht natürlichere Interpretation für Listen. Was fällt Ihnen ein, wenn Sie sich die folgende Typensignatur ansehen?

listApply :: [a -> b] -> [a] -> [b]

Die Idee, die Listen parallel aufzustellen und jede Funktion in der ersten auf das entsprechende Element der zweiten anzuwenden, ist einfach so verlockend. Unglücklicherweise für unseren alten Freund verstößtMonad diese einfache Operation gegen die Monadengesetze, wenn die Listen unterschiedlich lang sind. Aber es macht eine Geldstrafe Applicative, in welchem ​​Fall (<*>)wird eine Möglichkeit, eine verallgemeinerte Version von aneinander zu reihen zipWith, also können wir uns vielleicht vorstellen, es zu nennen fzipWith?


Diese Zipping-Idee schließt den Kreis. Erinnern Sie sich an das Mathe-Zeug früher über monoidale Funktoren? Wie der Name schon sagt, können Sie auf diese Weise die Struktur von Monoiden und Funktoren kombinieren, die beide bekannte Haskell-Typklassen sind:

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

class Monoid a where
    mempty :: a
    mappend :: a -> a -> a

Wie würden diese aussehen, wenn Sie sie in eine Schachtel legen und ein wenig aufrütteln würden? Von Functorbehalten wir die Idee einer Struktur unabhängig von ihrem Typparameter und von Monoidbehalten wir die Gesamtform der Funktionen bei:

class (Functor f) => MonoidalFunctor f where
    mfEmpty :: f ?
    mfAppend :: f ? -> f ? -> f ?

Wir wollen nicht davon ausgehen, dass es eine Möglichkeit gibt, ein wirklich "leeres" zu erstellen Functor, und wir können keinen Wert eines beliebigen Typs heraufbeschwören, also werden wir den Typ von mfEmptyas festlegen f ().

Wir möchten auch nicht erzwingen mfAppend, dass ein konsistenter Typparameter benötigt wird. Jetzt haben wir Folgendes:

class (Functor f) => MonoidalFunctor f where
    mfEmpty :: f ()
    mfAppend :: f a -> f b -> f ?

Wofür ist der Ergebnistyp mfAppend? Wir haben zwei beliebige Typen, von denen wir nichts wissen, daher haben wir nicht viele Optionen. Am sinnvollsten ist es, einfach beides zu behalten:

class (Functor f) => MonoidalFunctor f where
    mfEmpty :: f ()
    mfAppend :: f a -> f b -> f (a, b)

An diesem Punkt mfAppendist jetzt eindeutig eine verallgemeinerte Version von zipOn-Listen, und wir können Applicativeleicht rekonstruieren :

mfPure x = fmap (\() -> x) mfEmpty
mfApply f x = fmap (\(f, x) -> f x) (mfAppend f x)

Dies zeigt uns auch, dass dies puremit dem Identitätselement von a zusammenhängt Monoid, sodass andere gute Namen dafür alles sein können, was auf einen Einheitswert, eine Nulloperation oder dergleichen hindeutet.


Das war langwierig, um es zusammenzufassen:

  • (<*>) ist nur eine modifizierte Funktionsanwendung, sodass Sie sie entweder als "ap" oder "apply" lesen oder ganz wie bei einer normalen Funktionsanwendung entfernen können.
  • (<*>)Verallgemeinert auch grob zipWithauf Listen, so dass Sie es als "Zip-Funktoren mit" lesen können, ähnlich wie fmapals "Karte eines Funktors mit".

Die erste ist näher an der Absicht der ApplicativeTypklasse - wie der Name schon sagt - und das empfehle ich.

Tatsächlich ermutige ich die liberale Verwendung und Nichtaussprache aller aufgehobenen Anwendungsbetreiber :

  • (<$>), wodurch eine Einzelargumentfunktion in eine Functor
  • (<*>), die eine Multi-Argument-Funktion durch eine verkettet Applicative
  • (=<<), die eine Funktion bindet, die a in eine Monadvorhandene Berechnung eingibt

Alle drei sind im Kern nur reguläre Funktionsanwendungen, die ein wenig aufgewertet wurden.

CA McCann
quelle
6
@Colin Cochrane: Bist du sicher, dass du dort nicht "langatmig" falsch geschrieben hast? :) Aber hey, ich nehme es! Ich habe immer das Gefühl, dass Applicativeder funktionale, idiomatische Stil, den er fördert, nicht genug Liebe findet, daher konnte ich der Chance nicht widerstehen, seine Tugenden ein wenig zu preisen, um zu erklären, wie ich (nicht) ausspreche (<*>).
CA McCann
+1! Bitte zögern Sie nicht,
Greg Bacon
6
Hätte dieser Haskell Syntaxzucker für Applicative 's! So etwas wie [| f a b c d |](wie vom Originalpapier vorgeschlagen). Dann würden wir den <*>Kombinator nicht brauchen und Sie würden einen solchen Ausdruck als Beispiel für "Funktionsanwendung in einem Funktionskontext" bezeichnen
Tom Crockett
1
@FredOverflow: Nein, ich meinte Monad. Oder FunctoroderMonoid oder irgendetwas anderes, das einen gut etablierten Begriff hat, der weniger als drei Adjektive umfasst. "Anwendbar" ist lediglich ein wenig inspirierender, wenn auch einigermaßen beschreibender Name, der auf etwas geklatscht ist, das eher einen benötigt.
CA McCann
1
@pelotom: siehe [ stackoverflow.com/questions/12014524/… wo freundliche Leute mir zwei Möglichkeiten zeigten, um fast diese Notation zu bekommen.
AndrewC
21

Da ich keine Ambitionen habe, die technische Antwort von CA McCann zu verbessern , werde ich mich mit der flauschigeren befassen:

Wenn Sie purein etwas umbenennen könnten, das für Podunks wie mich freundlicher ist, wie würden Sie es nennen?

Als Alternative schlage ich einen anderen Namen vor , zumal die ständige Angst und der Verrat, die gegen die MonadVersion " return" geweint werden, kein Ende haben , ein anderes Ende haben, das seine Funktion auf eine Weise vorschlägt, die den zwingendsten imperativen Programmierern gerecht wird und das funktionalste von ... nun, hoffentlich kann sich jeder gleich beschweren über : inject.

Nimm einen Wert. "Injizieren" Sie es in dieFunctor , Applicative, Monadoder was-haben-Sie. Ich stimme für " inject" und habe diese Nachricht genehmigt.

BMeph
quelle
4
Normalerweise neige ich zu etwas wie "Einheit" oder "Aufzug", aber diese haben in Haskell bereits zu viele andere Bedeutungen. injectist ein ausgezeichneter Name und wahrscheinlich besser als meiner, obwohl als kleine Randnotiz "injizieren" in - glaube ich - Smalltalk und Ruby für eine Art Linksfalte verwendet wird. Ich habe diese Wahl des Namens jedoch nie verstanden ...
CA McCann
3
Dies ist ein sehr alter Thread, aber ich denke, dass injectin Ruby & Smalltalk verwendet wird, weil es so ist, als würden Sie einen Operator zwischen jedes Element in der Liste "einfügen". Zumindest habe ich so immer daran gedacht.
Jonathan Sterling
1
Um wieder abholen , dass alte Seiten thread: Sie sind nicht Betreiber Injizieren Sie ersetzen (Beseitigung) Konstruktoren , die bereits vorhanden sind. (Andersherum gesehen, sind Sie injizieren alte Daten in einen neuen Typen.) Für Listen, ist die Beseitigung nur foldr. (Sie ersetzen (:)und [], wobei (:)2 Argumente benötigt werden und []eine Konstante sind, daher foldr (+) 0 (1:2:3:[])1+2+3+0.) BoolEs ist nur if- then- else(zwei Konstanten, wählen Sie eine aus) und dafür Maybeheißt es maybe... Haskell hat keinen einzigen Namen / keine einzige Funktion dafür, da alle unterschiedliche Typen haben (im Allgemeinen ist elim nur Rekursion / Induktion)
niemand
@CAMcCann Smalltalk erhielt diesen Namen von einem Lied von Arlo Guthrie über den Entwurf für den Vietnamkrieg, in dem unglückliche junge Männer gesammelt, ausgewählt, manchmal abgelehnt und auf andere Weise injiziert wurden.
Tom Anderson
7

In Kürze:

  • <*>Sie können es anwenden nennen . So Maybe f <*> Maybe akann ausgesprochen werden , da gelten Maybe füberMaybe a .

  • Sie könnten umbenennen purezu of, wie viele JavaScript - Bibliotheken zu tun. In JS können Sie ein Maybemit erstellen Maybe.of(a).

Außerdem hat Haskells Wiki eine Seite auf die Aussprache der Sprache Operatoren hier

Marcelo Lazaroni
quelle
3
(<*>) -- Tie Fighter
(*>)  -- Right Tie
(<*)  -- Left Tie
pure  -- also called "return"

Quelle: Haskell Programming from First Principles von Chris Allen und Julie Moronuki

dmvianna
quelle
Soweit ich das beurteilen kann, haben sich diese Namen nicht genau durchgesetzt.
Feuer
@dfeuer warten auf die nächste Generation von Haskellern, die dieses Buch als primäres Lernmaterial verwenden.
dmvianna
1
Es könnte passieren. Die Namen sind jedoch schrecklich, da sie keinen Zusammenhang mit den Bedeutungen haben.
Feuer
1
@dfeuer, ich sehe nirgendwo eine gute Lösung. "ap" / "bewerben" ist so vage wie "Krawattenkämpfer". Alles ist Funktionsanwendung. Ein aus heiterem Himmel kommender Name könnte jedoch durch Verwendung Bedeutung erlangen. "Apple" ist ein gutes Beispiel. By the way, Monad Rückkehr ist Applicative rein. Keine Erfindungen hier.
dmvianna