Es ist eigentlich nur ein normaler Datenkonstruktor, der zufällig im Prelude definiert wird. Dies ist die Standardbibliothek, die automatisch in jedes Modul importiert wird.
Was vielleicht strukturell ist
Die Definition sieht ungefähr so aus:
data Maybe a = Just a
| Nothing
Diese Deklaration definiert einen Typ, Maybe a
der durch eine Typvariable parametrisiert wird. a
Dies bedeutet lediglich, dass Sie ihn mit jedem Typ anstelle von verwenden können a
.
Konstruieren und zerstören
Der Typ hat zwei Konstruktoren Just a
und Nothing
. Wenn ein Typ mehrere Konstruktoren hat, bedeutet dies, dass ein Wert des Typs mit nur einem der möglichen Konstruktoren erstellt worden sein muss. Für diesen Typ wurde ein Wert entweder über Just
oder erstellt Nothing
, es gibt keine anderen (fehlerfreien) Möglichkeiten.
Da Nothing
es keinen Parametertyp gibt, benennt es bei Verwendung als Konstruktor einen konstanten Wert, der Maybe a
für alle Typen vom Typ type ist a
. Der Just
Konstruktor verfügt jedoch über einen Typparameter. Wenn er als Konstruktor verwendet wird, verhält er sich wie eine Funktion von Typ a
bis Maybe a
, dh er hat den Typa -> Maybe a
Die Konstruktoren eines Typs erstellen also einen Wert dieses Typs. Die andere Seite der Dinge ist, wenn Sie diesen Wert verwenden möchten, und hier kommt der Mustervergleich ins Spiel. Im Gegensatz zu Funktionen können Konstruktoren in Musterbindungsausdrücken verwendet werden. Auf diese Weise können Sie Fallanalysen von Werten durchführen, die zu Typen mit mehr als einem Konstruktor gehören.
Um einen Maybe a
Wert in einer Musterübereinstimmung zu verwenden, müssen Sie für jeden Konstruktor ein Muster bereitstellen, wie folgt:
case maybeVal of
Nothing -> "There is nothing!"
Just val -> "There is a value, and it is " ++ (show val)
In diesem Fall würde das erste Muster übereinstimmen, wenn der Wert wäre Nothing
, und das zweite würde übereinstimmen, wenn der Wert mit konstruiert wurde Just
. Wenn der zweite übereinstimmt, bindet er auch den Namen val
an den Parameter, der an den Just
Konstruktor übergeben wurde, als der Wert erstellt wurde, mit dem Sie übereinstimmen.
Was vielleicht bedeutet
Vielleicht waren Sie bereits damit vertraut, wie das funktionierte; Maybe
Werte sind nicht wirklich magisch , sondern nur ein normaler alaskischer Haskell-Datentyp (ADT). Aber es wird ziemlich oft verwendet, weil es einen Typ, wie Integer
aus Ihrem Beispiel, effektiv in einen neuen Kontext "hebt" oder erweitert, in dem es einen zusätzlichen Wert ( Nothing
) hat, der einen Wertmangel darstellt! Das Typsystem erfordert dann , dass Sie für diesen Mehrwert zu überprüfen , bevor es Sie an dem erhalten lassen , Integer
die vielleicht da sein. Dies verhindert eine bemerkenswerte Anzahl von Fehlern.
Viele Sprachen verarbeiten diese Art von "No-Value" -Wert heutzutage über NULL-Referenzen. Tony Hoare, ein bedeutender Informatiker (er hat Quicksort erfunden und ist Turing-Preisträger), hält dies für seinen "Milliarden-Dollar-Fehler" . Der Vielleicht-Typ ist nicht der einzige Weg, dies zu beheben, aber er hat sich als effektiver Weg erwiesen, dies zu tun.
Vielleicht als Funktor
Die Idee, einen Typ in einen anderen zu transformieren, sodass Operationen für den alten Typ auch so transformiert werden können, dass sie für den neuen Typ funktionieren, ist das Konzept hinter der aufgerufenen Haskell-Typklasse Functor
, die Maybe a
eine nützliche Instanz von hat.
Functor
stellt eine aufgerufene Methode bereit fmap
, die Funktionen, die über Werte vom Basistyp (z. B. Integer
) liegen, Funktionen zuordnet, die über Werte vom angehobenen Typ (z. B. Maybe Integer
) reichen . Eine Funktion, die transformiert wurde fmap
, um an einem Maybe
Wert zu arbeiten, funktioniert folgendermaßen:
case maybeVal of
Nothing -> Nothing -- there is nothing, so just return Nothing
Just val -> Just (f val) -- there is a value, so apply the function to it
Wenn Sie also einen Maybe Integer
Wert m_x
und eine Int -> Int
Funktion haben f
, können Sie fmap f m_x
die Funktion f
direkt auf die anwenden , Maybe Integer
ohne sich Sorgen machen zu müssen, ob sie tatsächlich einen Wert hat oder nicht. Tatsächlich können Sie eine ganze Kette von angehobenen Integer -> Integer
Funktionen auf Maybe Integer
Werte anwenden und müssen sich nur noch Nothing
einmal explizit darum kümmern, wenn Sie fertig sind.
Vielleicht als Monade
Ich bin mir nicht sicher, wie vertraut Sie mit dem Konzept eines sind Monad
, aber Sie haben es zumindest schon einmal verwendet IO a
, und die Typensignatur IO a
sieht bemerkenswert ähnlich aus Maybe a
. Obwohl IO
es insofern besonders ist, als es seine Konstruktoren nicht für Sie verfügbar macht und daher nur vom Haskell-Laufzeitsystem "ausgeführt" werden kann, ist es Functor
neben dem auch noch ein Monad
. In der Tat gibt es einen wichtigen Sinn, in dem a Monad
nur eine besondere Art Functor
mit einigen zusätzlichen Funktionen ist, aber dies ist nicht der richtige Ort, um darauf einzugehen.
Wie auch immer, Monaden mögen IO
Zuordnungstypen zu neuen Typen, die "Berechnungen, die zu Werten führen" darstellen, und Sie können Funktionen Monad
über eine sehr fmap
ähnliche Funktion liftM
, die eine reguläre Funktion in eine "Berechnung umwandelt, die zu dem Wert führt, der durch Auswertung der Werte erhalten wird, in Typen umwandeln Funktion."
Sie haben wahrscheinlich erraten (wenn Sie so weit gelesen haben), dass dies Maybe
auch ein ist Monad
. Es stellt "Berechnungen dar, bei denen möglicherweise kein Wert zurückgegeben wird". Genau wie im fmap
Beispiel können Sie so eine ganze Reihe von Berechnungen durchführen, ohne nach jedem Schritt explizit nach Fehlern suchen zu müssen. Tatsächlich wird bei der Art und Weise, wie die Monad
Instanz aufgebaut ist, eine Berechnung der Maybe
Werte gestoppt , sobald eine gefunden Nothing
wird. Dies ist also wie ein sofortiger Abbruch oder eine wertlose Rückkehr mitten in einer Berechnung.
Du hättest vielleicht schreiben können
Wie ich bereits sagte, ist dem Maybe
Typ, der in die Sprachsyntax oder das Laufzeitsystem integriert ist, nichts inhärent . Wenn Haskell es nicht standardmäßig zur Verfügung gestellt hat, können Sie alle Funktionen selbst bereitstellen! Tatsächlich könnten Sie es trotzdem selbst mit unterschiedlichen Namen erneut schreiben und die gleiche Funktionalität erhalten.
Hoffentlich verstehen Sie den Maybe
Typ und seine Konstruktoren jetzt, aber wenn noch etwas unklar ist, lassen Sie es mich wissen!
Maybe
wo andere Sprachen verwenden würdennull
odernil
(mit bösenNullPointerException
lauern in jeder Ecke). Jetzt verwenden auch andere Sprachen dieses Konstrukt: Scala asOption
und sogar Java 8 haben denOptional
Typ.Die meisten aktuellen Antworten sind hochtechnische Erklärungen dafür
Just
und Freunde arbeiten. Ich dachte, ich könnte versuchen zu erklären, wofür es ist.Viele Sprachen haben einen solchen
null
Wert, der zumindest für einige Typen anstelle eines echten Werts verwendet werden kann. Dies hat viele Menschen sehr wütend gemacht und wurde allgemein als schlechter Schachzug angesehen. Trotzdem ist es manchmal nützlich, einen Wertnull
zu haben, der das Fehlen einer Sache anzeigt.Haskell löst dieses Problem, indem Sie explizit Orte markieren, an denen Sie a haben können
Nothing
(seine Version von anull
). Wenn Ihre Funktion normalerweise den Typ zurückgeben würdeFoo
, sollte sie stattdessen den Typ zurückgebenMaybe Foo
. Wenn Sie angeben möchten, dass kein Wert vorhanden ist, geben Sie zurückNothing
. Wenn Sie einen Wert zurückgeben möchtenbar
, sollten Sie stattdessen zurückgebenJust bar
.Wenn Sie also nicht haben können
Nothing
, brauchen Sie es nichtJust
. Wenn du kannstNothing
, brauchen SieJust
.Es ist nichts Magisches daran
Maybe
; Es basiert auf dem System vom Typ Haskell. Das heißt, Sie können alle üblichen Haskell- Mustervergleichstricks verwenden .quelle
Bei einem gegebenen Typ
t
ist ein Wert vonJust t
ein vorhandener Wert vom Typt
, wobeiNothing
bei dem ein Wert nicht erreicht werden kann, oder ein Fall, bei dem ein Wert bedeutungslos wäre.In Ihrem Beispiel ist ein negativer Saldo nicht sinnvoll. Wenn so etwas auftreten würde, wird es durch ersetzt
Nothing
.In einem anderen Beispiel könnte dies bei der Division verwendet werden, indem eine Divisionsfunktion definiert wird, die
a
undb
nimmt und zurückgibt,Just a/b
wenn sieb
ungleich Null ist, undNothing
ansonsten. Es wird oft so verwendet, als bequeme Alternative zu Ausnahmen oder wie in Ihrem früheren Beispiel, um Werte zu ersetzen, die keinen Sinn ergeben.quelle
Just
, wird Ihr Code nicht typecheck. Der Grund dafürJust
ist, die richtigen Typen beizubehalten. Es gibt einen Typ (eigentlich eine Monade, aber es ist einfacher, sich nur einen Typ vorzustellen)Maybe t
, der aus Elementen der FormJust t
und bestehtNothing
. DaNothing
hat TypMaybe t
, wird ein Ausdruck, der entwederNothing
oder einen Wert von Typ auswerten kann,t
nicht richtig typisiert. Wenn eine FunktionNothing
in einigen Fällen zurückgegeben wird, muss jeder Ausdruck, der diese Funktion verwendet, eine Möglichkeit haben, dies (isJust
oder eine case-Anweisung) zu überprüfen , um alle möglichen Fälle zu behandeln.Maybe t
ist nur ein Typ. Die Tatsache, dass es eineMonad
Instanz fürMaybe
gibt, verwandelt sie nicht in etwas, das kein Typ ist.Eine Gesamtfunktion a-> b kann für jeden möglichen Wert vom Typ a einen Wert vom Typ b finden.
In Haskell sind nicht alle Funktionen vollständig. In diesem speziellen Fall Funktion
lend
nicht vollständig - sie ist nicht für den Fall definiert, dass das Guthaben unter der Reserve liegt (obwohl es nach meinem Geschmack sinnvoller wäre, nicht zuzulassen, dass newBalance unter der Reserve liegt - wie es ist, können Sie 101 ausleihen ein Saldo von 100).Andere Designs, die sich mit nicht vollständigen Funktionen befassen:
lend
geschrieben werden, um den alten Saldo zurückzugeben, wenn die Bedingung für die Kreditvergabe nicht erfüllt istDies sind notwendige Designeinschränkungen in Sprachen, die nicht die Gesamtheit der Funktionen erzwingen können (z. B. Agda, dies führt jedoch zu anderen Komplikationen, z. B. Unvollständigkeit).
Das Problem bei der Rückgabe eines speziellen Werts oder beim Auslösen von Ausnahmen besteht darin, dass es für den Aufrufer leicht ist, die Behandlung einer solchen Möglichkeit versehentlich zu unterlassen.
Das Problem beim stillen Verwerfen eines Fehlers liegt ebenfalls auf der Hand: Sie beschränken die Möglichkeiten des Anrufers für die Funktion. Wenn beispielsweise das
lend
alte Guthaben zurückgegeben wird, kann der Anrufer nicht feststellen, ob sich das Guthaben geändert hat. Je nach Verwendungszweck kann dies ein Problem sein oder auch nicht.Die Lösung von Haskell zwingt den Aufrufer einer Teilfunktion, sich mit dem Typ like
Maybe a
oderEither error a
aufgrund des Rückgabetyps der Funktion zu befassen .Diese
lend
Definition ist eine Funktion, die nicht immer einen neuen Kontostand berechnet. Unter bestimmten Umständen wird kein neuer Kontostand definiert. Wir signalisieren dem Anrufer diesen Umstand, indem wir entweder den Sonderwert Nothing zurückgeben oder den neuen Kontostand in Just einwickeln. Der Anrufer hat nun die Freiheit zu wählen: Entweder das Versagen beim Ausleihen auf besondere Weise behandeln oder das alte Guthaben ignorieren und verwenden - zum Beispielmaybe oldBalance id $ lend amount oldBalance
.quelle
Die Funktion
if (cond :: Bool) then (ifTrue :: a) else (ifFalse :: a)
muss den gleichen Typ vonifTrue
und habenifFalse
.Wenn wir also schreiben
then Nothing
, müssen wirMaybe a
type in verwendenelse f
quelle
Nothing
undJust newBalance
explizit anzugeben.Just
.