Dank der verzögerten Auswertung kann ein Haskell-Programm nicht (fast nicht ) das tun, wie es aussieht.
Betrachten Sie dieses Programm:
main = putStrLn (show (quicksort [8, 6, 7, 5, 3, 0, 9]))
In einer eifrigen Sprache quicksort
würde dann zuerst laufen show
, dann putStrLn
. Die Argumente einer Funktion werden berechnet, bevor diese Funktion ausgeführt wird.
In Haskell ist es umgekehrt. Die Funktion wird zuerst ausgeführt. Die Argumente werden nur berechnet, wenn die Funktion sie tatsächlich verwendet. Und ein zusammengesetztes Argument wird wie eine Liste Stück für Stück berechnet, da jedes Stück davon verwendet wird.
Das erste , was in diesem Programm passiert, ist, dass es gestartet wird putStrLn
.
Die Implementierung von GHCputStrLn
funktioniert durch Kopieren der Zeichen des Arguments String in einen Ausgabepuffer. Aber wenn es in diese Schleife eintritt, show
ist es noch nicht gelaufen. Wenn das erste Zeichen aus der Zeichenfolge kopiert wird, wertet Haskell daher den Bruchteil der show
und quicksort
Aufrufe aus, die zur Berechnung dieses Zeichens erforderlich sind . Dann putStrLn
bewegt sich auf das nächste Zeichen. Die Ausführung aller drei Funktionen putStrLn
- show
, und quicksort
- ist also verschachtelt. quicksort
wird schrittweise ausgeführt und hinterlässt ein Diagramm mit nicht bewerteten Thunks, um sich daran zu erinnern, wo es aufgehört hat.
Dies unterscheidet sich grundlegend von dem, was Sie erwarten könnten, wenn Sie mit einer anderen Programmiersprache vertraut sind. Es ist nicht einfach zu visualisieren, wie quicksort
sich Haskell in Bezug auf Speicherzugriffe oder sogar die Reihenfolge der Vergleiche tatsächlich verhält. Wenn Sie nur das Verhalten und nicht den Quellcode beobachten könnten, würden Sie nicht erkennen, was es als Quicksort tut .
Beispielsweise partitioniert die C-Version von Quicksort alle Daten vor dem ersten rekursiven Aufruf. In der Haskell-Version wird das erste Element des Ergebnisses berechnet (und kann sogar auf Ihrem Bildschirm angezeigt werden), bevor die erste Partition ausgeführt wird - tatsächlich bevor überhaupt daran gearbeitet wird greater
.
PS Der Haskell-Code wäre schneller, wenn er die gleiche Anzahl von Vergleichen wie Quicksort durchführen würde. der Code geschrieben hat doppelt so viele Vergleiche da lesser
und greater
spezifiziert unabhängig berechnet werden, zwei lineare Scans durch die Liste zu tun. Natürlich ist es im Prinzip möglich, dass der Compiler intelligent genug ist, um die zusätzlichen Vergleiche zu eliminieren. oder der Code könnte geändert werden, um zu verwenden Data.List.partition
.
PPS Das klassische Beispiel für Haskell-Algorithmen, die sich nicht so verhalten, wie Sie es erwartet haben, ist das Eratosthenes-Sieb für die Berechnung von Primzahlen.