Verständnis Haskell Typ-Signaturen
Ich bin in den Prozess des Lehrens selbst Haskell und ich wunderte mich über die folgende Signaturen:
Prelude> :t ($)
($) :: (a -> b) -> a -> b
Prelude>
Wie soll ich das verstehen (kein Wortspiel beabsichtigt), dass?
Semi-ähnliches Ergebnis ist auch erweist sich als rätselhaft:
Prelude> :t map
map :: (a -> b) -> [a] -> [b]
Prelude>
Du musst angemeldet sein, um einen Kommentar abzugeben.
Beginnen werde ich mit
map
. Diemap
Funktion wendet eine operation auf jedes element einer Liste. Wenn ichDann könnte ich diese anwenden, um eine ganze Liste von
Int
s mitmap
:So, wenn man sich die Typ-Signatur
Du wirst sehen, dass das erste argument ist
(a -> b)
, die nur eine Funktion, die einena
und gibt einenb
. Das zweite argument ist[a]
, die eine Liste von Werten des Typsa
, und der Rückgabetyp[b]
eine Liste von Werten des Typsb
. Also im Klartext, diemap
- Funktion wendet eine Funktion auf jedes element einer Liste von Werten, und gibt dann die Werte als eine Liste.Dies ist, was macht
map
eine höherer Ordnung Funktion, es nimmt eine Funktion als argument und macht Sachen mit ihm. Eine andere Weise, darauf zu schauenmap
ist, fügen Sie einige Klammern geben Sie die Signatur, um esSo können Sie auch denken, es als eine Funktion transformiert eine Funktion von
a
zub
in eine Funktion aus[a]
zu[b]
.Die Funktion
($)
hat der TypVerwendet und ist wie
Alle es tut, ist, nehmen Sie, was der Recht, in diesem Fall
1 + 1
und übergibt Sie an die Funktion auf der Links, hieradd3
. Warum ist das wichtig? Es hat eine handliche Festigkeit, oder Rangfolge, die es macht, entsprichtAlso auch immer um das Recht bekommt, im wesentlichen eingewickelt in Klammern vor der übergabe an den linken. Dies macht es nützlich für die Verkettung mehrere Funktionen zusammen:
ist schöner als
weil Sie nicht haben, zu schließen Klammern.
add3 $ add3 $ add3 $ add3 $ 1 + 1
undadd3 . add3 . add3 . add3 $ 1 + 1
?Naja, wie schon gesagt,
$
leicht verstanden werden können, wenn Sie einfach vergessen, über currying und sehen, wie es, sagen wir, in C++Aber tatsächlich, es gibt mehr zu diesem als nur die Anwendung einer Funktion auf einen Wert! Die offensichtliche ähnlichkeit zwischen den Signaturen der
$
undmap
hat in der Tat eine ziemlich Tiefe der Kategorie-Theorie Bedeutung: beide sind Beispiele für die morphismus-Aktion der Funktor!In der Kategorie Hask, mit denen wir arbeiten die ganze Zeit, Objekte Typen. (Das ist ein bisschen confusionsome, aber keine Sorge). Die morphismen sind die Funktionen.
Den meisten gut bekannt (endo-)funktoren sind die, die eine Instanz von die namensgebende Art Klasse. Aber eigentlich, mathematisch gesehen, ein Funktor ist nur etwas, dass die Karten beide Objekte auf Objekte und morphismen auf morphismen1.
map
(Wortspiel beabsichtigt, nehme ich an!) ein Beispiel: es nimmt ein Objekt (also Typ)A
und ordnet Sie zu einer Art[A]
. Und für zwei ArtenA
undB
dauert es einen morphismus (d.h. Funktion)A -> B
, und ordnet Sie der entsprechenden Liste-Funktion von Typ[A] -> [B]
.Dies ist nur ein spezieller Fall der functor-Klasse-Signatur operation:
Mathematik erfordert nicht diese
fmap
zu haben, einen Namen, obwohl. Und so kann auch die Identität Funktor, die einfach ordnet jedem Typ selbst. Und jeden morphismus auf sich:"Identität" existiert offensichtlich mehr im Allgemeinen, Sie können auch die map-Werte eines beliebigen Typs zu sich selbst.
Und sicher genug, eine mögliche Implementierung ist dann
1Geist, nicht alles, dass die Karten-Objekte und-morphismen ist ein Funktor... es muss genügen Funktor Gesetze.
($)
ist nur Funktion der Anwendung. Es wird eine Funktion des Typsa->b
ein argument vom Typa
werden, gilt die Funktion und gibt einen Wert vom Typb
.map
ist ein wunderbares Beispiel dafür, wie das Lesen einer Funktion geben Sie die Signatur hilft es zu verstehen.map
's erste argument ist eine Funktion, die nimmta
und zurückb
werden soll, das zweite argument ist eine Liste von Typ[a]
.So
map
wendet eine Funktion vom Typa->b
um eine Liste dera
Werte. Und der Ergebnis-Typ ist in der Tat der Typ[b]
- eine Liste vonb
Werte!(a->b)->[a]->[b]
kann interpretiert werden als "Akzeptiert eine Funktion und eine Liste und gibt eine andere Liste", und auch als "Akzeptiert eine Funktion vom Typa->b
und gibt eine weitere Funktion des Typs[a]->[b]
".Wenn man es auf diese Weise
map
"upgrade" f (der Begriff "Aufzug" wird oft in diesem Zusammenhang) zu arbeiten, die auf Listen: wenndouble
ist eine Funktion, Doppel-integer, dannmap double
ist eine Funktion, Doppel-jede ganze Zahl in einer Liste.f :: a -> b -> c
undf :: a -> (b -> c)
gleichwertig sind. Diese ist wirklich, weil Haskell-Funktionen werden teilweise angewendet (anwenden f einige a gibt eine Funktion von b nach c). Wenng :: (a -> b) -> c
wird stattdessen verwendet, es ist speziell Kennzeichnen, Sie müssen eine Funktion g ist, und dass Sie nicht nur teilweise gelten.g) die(a -> b)
entspricht(*2)
, daa
undb
sindInt
in unserem Beispiel, und(*2)::Int -> Int
.(a -> b)
ist eine Funktion, die akzeptiert einen einzelnen Wert vom Typa
und gibt einen einzelnen Wert zurück vom Typb
. Abermap (*2)
, aufgrund der teilweisen Anwendung, ist der Typ[a]->[b]
- akzeptiert eine Liste vona
aus und liefert eine Liste vonb
.