Lassen Sie uns zunächst unterscheiden zwischen dem Erlernen der abstrakten Konzepte und dem Erlernen spezifischer Beispiele .
Sie werden nicht allzu weit kommen, wenn Sie alle spezifischen Beispiele ignorieren, und das aus dem einfachen Grund, weil sie absolut allgegenwärtig sind. Tatsächlich existieren die Abstraktionen zu einem großen Teil, weil sie die Dinge, die Sie sowieso tun würden, mit den spezifischen Beispielen vereinen.
Die Abstraktionen selbst sind zwar nützlich , aber nicht sofort notwendig. Man kann ziemlich weit kommen, wenn man die Abstraktionen komplett ignoriert und einfach die verschiedenen Typen direkt verwendet. Sie werden sie irgendwann verstehen wollen, aber Sie können später immer noch darauf zurückkommen. Tatsächlich kann ich fast garantieren, dass Sie sich in diesem Fall die Stirn schlagen und sich fragen, warum Sie die ganze Zeit auf die harte Tour verbracht haben, anstatt die praktischen Allzweckwerkzeuge zu verwenden.
Nehmen Sie Maybe a
als Beispiel. Es ist nur ein Datentyp:
data Maybe a = Just a | Nothing
Es ist alles andere als selbstdokumentierend; Es ist ein optionaler Wert. Entweder Sie haben "nur" etwas von Typ a
, oder Sie haben nichts. Angenommen, Sie haben eine Art Nachschlagefunktion, die darauf hinweist Maybe String
, String
dass ein möglicherweise nicht vorhandener Wert gesucht wird . Sie stimmen also mit dem Wert überein, um festzustellen, um welchen Wert es sich handelt:
case lookupFunc key of
Just val -> ...
Nothing -> ...
Das ist alles!
Wirklich, sonst brauchen Sie nichts. Kein Functor
s oder Monad
s oder irgendetwas anderes. Diese drücken übliche Arten der Verwendung von Maybe a
Werten aus ... aber sie sind nur Redewendungen, "Entwurfsmuster", wie auch immer Sie es nennen möchten.
Der einzige Ort, an dem Sie es wirklich nicht ganz vermeiden können, ist mit IO
, aber das ist sowieso eine mysteriöse Black Box. Es lohnt sich also nicht zu versuchen, zu verstehen, was es bedeutet, als Monad
was oder was auch immer.
In der Tat, hier ist ein Spickzettel für alles, was Sie für den Moment wirklich wissen müssen IO
:
Wenn etwas einen Typ IO a
hat, bedeutet das, dass es eine Prozedur ist , die etwas tut und einen a
Wert ausspuckt .
Wenn Sie einen Codeblock mit der do
Notation haben, schreiben Sie so etwas:
do -- ...
inp <- getLine
-- etc...
... bedeutet , die Prozedur rechts von auszuführen<-
und das Ergebnis dem Namen links zuzuweisen.
Wenn Sie so etwas haben:
do -- ...
let x = [foo, bar]
-- etc...
... bedeutet, den Wert des einfachen Ausdrucks (keine Prozedur) rechts von =
dem Namen links zuzuweisen .
Wenn Sie etwas dort ablegen, ohne einen Wert zuzuweisen:
do putStrLn "blah blah, fishcakes"
... bedeutet, eine Prozedur auszuführen und alles zu ignorieren, was sie zurückgibt. Einige Prozeduren haben den Typ IO ()
- der ()
Typ ist eine Art Platzhalter, der nichts aussagt , was bedeutet, dass die Prozedur etwas tut und keinen Wert zurückgibt . So ähnlich wie eine void
Funktion in anderen Sprachen.
Wenn Sie den gleichen Vorgang mehrmals ausführen, kann dies zu unterschiedlichen Ergebnissen führen. das ist so eine idee. Aus diesem Grund gibt es keine Möglichkeit, das IO
aus einem Wert zu "entfernen" , da etwas in IO
kein Wert ist, sondern eine Prozedur, um einen Wert zu erhalten.
Die letzte Zeile in einem do
Block muss eine einfache Prozedur ohne Zuweisung sein, wobei der Rückgabewert dieser Prozedur der Rückgabewert für den gesamten Block wird. Wenn der Rückgabewert einen bereits zugewiesenen Wert verwenden soll, nimmt die return
Funktion einen einfachen Wert und gibt Ihnen eine No-Op-Prozedur, die diesen Wert zurückgibt.
Davon abgesehen hat es nichts Besonderes IO
. Diese Prozeduren sind eigentlich reine Werte, und Sie können sie weitergeben und auf verschiedene Arten kombinieren. Nur wenn sie in einem do
Block ausgeführt werden, der von irgendwoher aufgerufen wird main
, tun sie etwas.
In so etwas wie diesem äußerst langweiligen, stereotypen Beispielprogramm:
hello = do putStrLn "What's your name?"
name <- getLine
let msg = "Hi, " ++ name ++ "!"
putStrLn msg
return name
... Sie können es wie ein Imperativprogramm ablesen. Wir definieren eine Prozedur mit dem Namen hello
. Bei der Ausführung wird zuerst eine Prozedur zum Drucken einer Nachricht ausgeführt, in der Sie nach Ihrem Namen gefragt werden. Als nächstes führt es eine Prozedur aus, die eine Eingabezeile liest und das Ergebnis zuweist name
. dann weist es dem Namen einen Ausdruck zu msg
; dann druckt es die Nachricht aus; Dann wird der Name des Benutzers als Ergebnis des gesamten Blocks zurückgegeben. Da a name
ist String
, bedeutet dies, dass hello
es sich um eine Prozedur handelt, die a zurückgibt String
, also einen Typ hat IO String
. Und jetzt können Sie diese Prozedur an einer anderen Stelle ausführen, genau wie sie ausgeführt wird getLine
.
Pfff, Monaden. Wer braucht sie?