Obwohl veränderbare Variablen in Haskell verwendet werden können, wie andere Kommentatoren zeigen, ist es kein guter Stil: Mutationen sollten in den meisten Fällen nicht verwendet werden.
Die Funktion inc
akzeptiert ihr Argument nach Wert, dh sie ändert ihr Argument nicht. Außerdem behalten die von let
deklarierten Variablen ihre Anfangswerte bei, so dass Sie sie nicht ändern können.
Wie schreibt man, wenn keine Variable jemals geändert werden kann? Die Antwort ist:
Glücklicherweise müssen Sie die Rekursion selten selbst schreiben, da sich die meisten rekursiven Muster bereits in der Standardbibliothek befinden.
In Ihrem Fall müssen Sie mehrere E / A-Aktionen durchführen und den endgültigen Wert der beiden Zähler zurückgeben. Beginnen wir mit einer Aktion:
%Vor% Hier deklarieren wir eine lokale Funktion mit 2 Parametern: die aktuellen Werte der Zähler, verpackt in einem Tupel (Int, Int)
(eine Struktur in anderen Sprachen) und aktueller Iteration Int
. Die Funktion führt IO-Aktionen durch und gibt modifizierte Werte der Zähler IO (Int, Int)
zurück. Dies alles wird in seinem Typ angezeigt:
ping
gibt einen Wert von IO String
type zurück. Um es zu vergleichen, benötigen Sie ein String
ohne IO
. Um dies zu tun, sollten Sie >>=
function:
Da dieses Muster häufig ist, kann es so geschrieben werden
%Vor% Aber die Bedeutung ist genau die gleiche (Compiler übersetzt do
Notation in Anwendungen von >>=
).
Die Verarbeitung zeigt einige häufigere Muster:
%Vor% Hier ist if
kein Imperativ if
, sondern eher ein ternärer condition ? value1 : value2
Operator in anderen Sprachen. Beachten Sie auch, dass unsere Funktion tryOnePing
(c, f) akzeptiert und entweder (c+1, f)
oder (c, f+1)
zurückgibt. Wir haben Tupel benutzt, da wir nur mit 2 Zählern arbeiten müssen. Bei einer großen Anzahl von Zählern müssten wir einen Strukturtyp deklarieren und benannte Felder verwenden.
Der Wert des gesamten If-Konstrukts ist ein Tupel (Int, Int). ping
ist eine IO-Aktion, also muss tryOnePing
auch eine IO-Aktion sein. Die Funktion return
ist keine zwingende Rückgabe, sondern eine Möglichkeit, (Int, Int)
in IO (Int, Int)
zu konvertieren.
Also, wie wir tryOnePing haben, müssen wir eine Schleife schreiben, um es 1000 Mal auszuführen. Ihre forM_
war keine gute Wahl:
_
gibt an, dass der letzte Wert der Zähler entfernt wird, anstatt ihn zurückzugeben Sie brauchen hier nicht forM_
, sondern foldM
foldM
führt eine IO-Aktion aus, die von jedem Element der Liste parametrisiert wird, und übergibt einen bestimmten Zustand zwischen Iterationen, in unserem Fall die beiden Zähler. Es akzeptiert den Anfangszustand und gibt den Endzustand zurück. Da IO-Aktionen ausgeführt werden, gibt es natürlich IO (Int, Int) zurück, also müssen wir >>=
verwenden, um es erneut zur Anzeige zu extrahieren:
In Haskell können Sie so genannte "eta reductions" durchführen, dh Sie können dieselben Bezeichner von beiden Seiten einer Funktionsdeklaration entfernen. Z.B. \foo -> bar foo
ist identisch mit nur bar
. Also in diesem Fall mit >>=
kannst du schreiben:
ist viel kürzer als do
notation:
Beachten Sie auch, dass Sie nicht zwei Zähler haben müssen: Wenn Sie 3000 Erfolge haben, dann haben Sie 7000 Fehler. So wird der Code:
%Vor%Schließlich ist es in Haskell gut, IO-Aktionen von Nicht-IO-Code zu trennen. Daher ist es besser, alle Ergebnisse von Pings in einer Liste zu sammeln und dann erfolgreiche Pings darin zu zählen:
%Vor%Beachten Sie, dass wir vermieden haben, insgesamt zu erhöhen.
Es kann noch kürzer geschrieben werden, erfordert aber mehr Geschick beim Lesen und Schreiben. Mach dir keine Sorgen, du wirst diese Tricks bald lernen:
%Vor% Variablen sind in Haskell unveränderlich. Wenn Sie inc f
aufrufen, wird ein Wert von 0 + 1
zurückgegeben, den Sie sofort ignorieren. Der Wert von f
ist 0
und bleibt dies für alle Zeit.
Tags und Links haskell