Eine der Kernideen in der funktionalen Programmierung ist Algorithmen zum Ausdruck bringen als Daten Transformationen. In einer faulen Sprache wie Haskell können wir sogar einen Schritt weiter gehen und faule Datenstrukturen als verdinglichte Berechnungen betrachten. In einem sehr realen Sinn sind die Listen von Haskell eher Schleifen als normale verkettete Listen: Sie können inkrementell berechnet werden und müssen nicht auf einmal im Speicher vorhanden sein. Gleichzeitig erhalten wir viele der Vorteile eines Datentyps wie die Fähigkeit, ihn zu übergeben und ihn mit einem Mustervergleich zu überprüfen.
In diesem Sinne besteht der "Trick" für das Ausdrücken einer for-Schleife mit einem Index darin, eine Liste aller möglichen Werte zu erstellen. Ihr Beispiel ist wahrscheinlich die einfachste Fall: i
nimmt alle Werte 0
-255
, so können wir Haskells Einbau-Notation für Bereiche verwendet werden:
[0..255]
Auf einem hohen Niveau, das ist Haskells Äquivalent for (i = 0 to 255)
; Wir können dann die tatsächliche Logik in der Schleife ausführen, indem wir diese Liste entweder durch eine rekursive Funktion oder eine Funktion höherer Ordnung aus der Standardbibliothek durchlaufen. (Die zweite Option ist sehr bevorzugt.)
Diese besondere Logik eignet sich gut für eine fold
. Mit einer Falte können wir eine Liste nach Elementen aufnehmen und ein Ergebnis erstellen. Bei jedem Schritt erhalten wir einen Listenpunkt und den Wert unseres bisherigen Ergebnisses. In diesem speziellen Fall wollen wir die Liste von links nach rechts verarbeiten, während wir einen Index inkrementieren, also können wir foldl
; Der eine schwierige Teil ist, dass es die Liste rückwärts produziert.
Hier ist die Art von foldl
:
foldl :: (b -> a -> b) -> b -> [a] -> b
So ist unsere Funktion in unserem Zwischenwert nimmt und ein Listenelement und erzeugt einen aktualisierten Zwischenwert. Da wir eine Liste erstellen und einen Index verfolgen, ist unser Zwischenwert ein Paar, das beides enthält. Dann, wenn wir das Endergebnis haben, können wir den idx
Wert ignorieren und umgekehrt die endgültige Liste erhalten wir:
a = let (result, _) = foldl step ([], 1) [0..255] in reverse result
where step (a, idx) i
| someCondition i = (idx:a, idx + 1)
| otherwise = (0:a, idx)
In der Tat, das Muster eine Liste der Transformation, während Spur einiger Zwischenzustand zu halten (idx
in diesem Fall) ist üblich genug, so dass es eine eigene Funktion in Bezug auf die State
Art hat. Die Kern Abstraktion ist ein bisschen mehr beteiligt (lesen Sie durch ["Sie hätten Monaden erfunden"] [Sie] für eine gute Einführung), aber der resultierende Code ist eigentlich recht angenehm zu lesen (außer für die Importe, ich denke: P) :
import Control.Applicative
import Control.Monad
import Control.Monad.State
a = evalState (mapM step [0..255]) 1
where step i
| someCondition i = get <* modify (+ 1)
| otherwise = return 0
die Idee ist, dass wir über [0..255]
Karte während irgendeinen Staates (der Wert von idx
) im Hintergrund zu verfolgen. evalState
ist, wie wir alle Rohrleitungen zusammenbringen und nur unser Endergebnis bekommen. Die Funktion step
wird auf jedes Element der Eingabeliste angewendet und kann auch auf den Status zugreifen oder diesen ändern.
Der erste Fall der step
Funktion ist interessant. Der Operator <*
sagt ihm, das Ding auf der linken Seite zuerst zu tun, das Ding auf der rechten zweiten , aber den Wert auf der linken Seite zurückgeben. Dadurch können wir den aktuellen Status abrufen, ihn inkrementieren, aber immer noch den Wert zurückgeben, bevor inkrementiert wurde. Die Tatsache, dass unser Staatsbegriff eine erstklassige Entität ist und wir Bibliotheksfunktionen wie <*
haben können, ist sehr mächtig - ich habe festgestellt, dass dieses spezielle Idiom wirklich nützlich für das Traversieren von Bäumen ist und andere ähnliche Idiome für anderen Code recht nützlich waren.
Wirklich gute Antwort. Ich war mir der staatlichen Monade bis zu diesem Punkt nicht bewusst. Vielen Dank! – fuji