Ich verstehe nicht, was "Heben" ist. Sollte ich zuerst Monaden verstehen, bevor ich verstehe, was ein "Aufzug" ist? (Ich bin auch völlig unwissend über Monaden :) Oder kann mir jemand das mit einfachen Worten erklären?
Ich verstehe nicht, was "Heben" ist. Sollte ich zuerst Monaden verstehen, bevor ich verstehe, was ein "Aufzug" ist? (Ich bin auch völlig unwissend über Monaden :) Oder kann mir jemand das mit einfachen Worten erklären?
Antworten:
Das Heben ist eher ein Entwurfsmuster als ein mathematisches Konzept (obwohl ich erwarte, dass mich hier jemand widerlegt, indem er zeigt, wie Aufzüge eine Kategorie oder etwas sind).
Normalerweise haben Sie einen Datentyp mit einem Parameter. Etwas wie
data Foo a = Foo { ...stuff here ...}
Angenommen , Sie feststellen , dass viele Anwendungen von Foo
numerischen Typen nehmen ( Int
, Double
usw.) und Sie halten, die zu schreiben Code, der diese Zahlen auspackt, ergänzt oder vervielfacht sie, und dann wickelt sie sichern. Sie können dies kurzschließen, indem Sie den Unwrap-and-Wrap-Code einmal schreiben. Diese Funktion wird traditionell als "Aufzug" bezeichnet, da sie folgendermaßen aussieht:
liftFoo2 :: (a -> b -> c) -> Foo a -> Foo b -> Foo c
Mit anderen Worten, Sie haben eine Funktion, die eine Funktion mit zwei Argumenten (z. B. den (+)
Operator) in die entsprechende Funktion für Foos umwandelt.
Jetzt können Sie also schreiben
addFoo = liftFoo2 (+)
Bearbeiten: Weitere Informationen
Sie können natürlich haben liftFoo3
, liftFoo4
und so weiter. Dies ist jedoch häufig nicht erforderlich.
Beginnen Sie mit der Beobachtung
liftFoo1 :: (a -> b) -> Foo a -> Foo b
Aber das ist genau das gleiche wie fmap
. Also eher als liftFoo1
du schreiben würdest
instance Functor Foo where
fmap f foo = ...
Wenn Sie wirklich vollständige Regelmäßigkeit wollen, können Sie sagen
liftFoo1 = fmap
Wenn Sie Foo
einen Funktor machen können, können Sie ihn vielleicht zu einem anwendbaren Funktor machen. Wenn Sie schreiben können, liftFoo2
sieht die anwendbare Instanz tatsächlich so aus :
import Control.Applicative
instance Applicative Foo where
pure x = Foo $ ... -- Wrap 'x' inside a Foo.
(<*>) = liftFoo2 ($)
Der (<*>)
Operator für Foo hat den Typ
(<*>) :: Foo (a -> b) -> Foo a -> Foo b
Es wendet die umschlossene Funktion auf den umschlossenen Wert an. Wenn Sie also implementieren liftFoo2
können, können Sie dies in Bezug darauf schreiben. Oder Sie können es direkt implementieren und sich nicht darum kümmern liftFoo2
, da das Control.Applicative
Modul enthält
liftA2 :: Applicative f => (a -> b -> c) -> f a -> f b -> f c
und ebenso gibt es liftA
und liftA3
. Sie werden jedoch nicht sehr oft verwendet, da es einen anderen Operator gibt
(<$>) = fmap
So können Sie schreiben:
result = myFunction <$> arg1 <*> arg2 <*> arg3 <*> arg4
Der Begriff myFunction <$> arg1
gibt eine neue Funktion zurück, die in Foo eingeschlossen ist. Dies kann wiederum auf das nächste Argument angewendet werden, indem (<*>)
usw. verwendet wird. Anstatt jetzt eine Liftfunktion für jede Arität zu haben, haben Sie nur noch eine Reihe von Anwendungen.
lift id == id
und lift (f . g) == (lift f) . (lift g)
.
id
und .
sind der Identitätspfeil und die Pfeilzusammensetzung einer Kategorie. Wenn Sie von Haskell sprechen, ist die fragliche Kategorie normalerweise "Hask", dessen Pfeile Haskell-Funktionen sind (mit anderen Worten, id
und .
beziehen sich auf die Haskell-Funktionen, die Sie kennen und lieben).
instance Functor Foo
, nicht instance Foo Functor
, nicht wahr? Ich würde mich selbst bearbeiten, bin mir aber nicht 100% sicher.
Pauls und Yairchus sind beide gute Erklärungen.
Ich möchte hinzufügen, dass die Funktion, die aufgehoben wird, eine beliebige Anzahl von Argumenten haben kann und dass sie nicht vom gleichen Typ sein müssen. Sie können beispielsweise auch einen liftFoo1 definieren:
liftFoo1 :: (a -> b) -> Foo a -> Foo b
Im Allgemeinen wird das Heben von Funktionen, die 1 Argument annehmen, in der Typklasse erfasst Functor
, und die Hebeoperation wird aufgerufen fmap
:
fmap :: Functor f => (a -> b) -> f a -> f b
Beachten Sie die Ähnlichkeit mit liftFoo1
dem Typ. In der Tat können Sie, wenn Sie haben liftFoo1
, Foo
eine Instanz von erstellen Functor
:
instance Functor Foo where
fmap = liftFoo1
Darüber hinaus wird die Verallgemeinerung des Aufhebens auf eine beliebige Anzahl von Argumenten als Anwendungsstil bezeichnet . Tauchen Sie erst ein, wenn Sie das Aufheben von Funktionen mit einer festen Anzahl von Argumenten verstanden haben. Aber wenn Sie dies tun, hat Learn you a Haskell ein gutes Kapitel dazu. Die Typeclassopedia ist ein weiteres gutes Dokument, das Functor und Applicative beschreibt (sowie andere Typklassen; scrollen Sie zum rechten Kapitel in diesem Dokument).
Hoffe das hilft!
Beginnen wir mit einem Beispiel (für eine übersichtlichere Darstellung wird ein Leerraum hinzugefügt):
> import Control.Applicative
> replicate 3 'a'
"aaa"
> :t replicate
replicate :: Int -> b -> [b]
> :t liftA2
liftA2 :: (Applicative f) => (a -> b -> c) -> (f a -> f b -> f c)
> :t liftA2 replicate
liftA2 replicate :: (Applicative f) => f Int -> f b -> f [b]
> (liftA2 replicate) [1,2,3] ['a','b','c']
["a","b","c","aa","bb","cc","aaa","bbb","ccc"]
> ['a','b','c']
"abc"
liftA2
wandelt eine Funktion einfacher Typen in eine Funktion derselben Typen um, die in eine eingeschlossen sindApplicative
, z. B. Listen IO
usw.
Ein weiterer üblicher Aufzug ist lift
von Control.Monad.Trans
. Es wandelt eine monadische Handlung einer Monade in eine Handlung einer transformierten Monade um.
Im Allgemeinen hebt "lift" eine Funktion / Aktion in einen "Wrapped" -Typ um (damit die ursprüngliche Funktion "under the Wraps" funktioniert).
Der beste Weg, dies und Monaden usw. zu verstehen und zu verstehen, warum sie nützlich sind, besteht wahrscheinlich darin, sie zu codieren und zu verwenden. Wenn Sie zuvor etwas codiert haben, von dem Sie vermuten, dass es davon profitieren kann (dh der Code wird dadurch kürzer usw.), probieren Sie es einfach aus und Sie werden das Konzept leicht verstehen.
Heben ist ein Konzept, mit dem Sie eine Funktion in eine entsprechende Funktion innerhalb einer anderen (normalerweise allgemeineren) Einstellung umwandeln können
Schauen Sie sich http://haskell.org/haskellwiki/Lifting an
Nach diesem glänzenden tutorial , ist ein Funktors einige Container (wie Maybe<a>
, List<a>
oder Tree<a>
das kann Speicherelementen irgendeines anderen Typs a
). Ich habe die Java-Generika-Notation <a>
für den Elementtyp verwendet a
und stelle mir die Elemente als Beeren im Baum vor Tree<a>
. Es gibt eine Funktion fmap
, die eine Elementkonvertierungsfunktion übernimmt, a->b
und einen Container functor<a>
. Es gilt a->b
für jedes Element des Containers, in das es effektiv umgewandelt wird functor<b>
. Wenn nur das erste Argument angegeben wird a->b
, fmap
wartet auf das functor<a>
. Das heißt, a->b
allein durch die Lieferung wird diese Funktion auf Elementebene in die Funktion umgewandelt functor<a> -> functor<b>
, die über Containern ausgeführt wird. Dies nennt man Hebender Funktion. Da der Container auch als Funktor bezeichnet wird , sind die Funktoren anstelle der Monaden eine Voraussetzung für das Heben. Monaden sind eine Art "parallel" zum Heben. Beide verlassen sich auf den Functor-Begriff und tun dies auch f<a> -> f<b>
. Der Unterschied besteht darin, dass das Heben a->b
für die Konvertierung verwendet wird, während Monad vom Benutzer definiert werden muss a -> f<b>
.
r
bis zu einem Typ (verwenden wir c
für Abwechslung) sind Funktoren. Sie "enthalten" keine c
. In diesem Fall ist fmap eine Funktionszusammensetzung, die eine a -> b
Funktion und eine r -> a
Eins übernimmt , um Ihnen eine neue r -> b
Funktion zu geben . Immer noch keine Container. Wenn ich könnte, würde ich es auch für den letzten Satz noch einmal notieren.
fmap
eine Funktion und "wartet" nicht auf irgendetwas; Der "Container", der ein Funktor ist, ist der springende Punkt beim Heben. Außerdem sind Monaden eher eine doppelte Idee zum Heben: Mit einer Monade können Sie etwas verwenden, das einige Male positiv angehoben wurde, als wäre es nur einmal angehoben worden - dies wird besser als Abflachen bezeichnet .
To wait
, to expect
, to anticipate
sind Synonyme. Mit "Funktion wartet" meinte ich "Funktion antizipiert".
b = 5 : a
und f 0 = 55
f n = g n
beide beinhalten eine Pseudomutation des "Containers". Auch die Tatsache, dass Listen normalerweise vollständig im Speicher gespeichert sind, während Funktionen normalerweise als Berechnung gespeichert werden. Aber Memoizing / Monorphic-Listen, die nicht zwischen Anrufen gespeichert werden, brechen beide den Mist aus dieser Idee heraus.