[Inhalt]...[Einleitung]...[Strikte Auswertung]
Definiert ist eine konstante Funktion "
(Pseudocode)
Für das Ergebnis der Funktion ist der Wert des aktuellen Parameters irrelevant. Er wird
von einem Interpreter deshalb nie ausgewertet. Aus diesem Grund führt der letzte Aufruf
mit der Division durch Null als Parameter nicht zu einem Fehler.
Solange also der Wert eines Ausdrucks (einer Funktion) nicht benötigt wird, behandelt
"Haskell" diesen als eine Definition. Als solche werden auch entsprechende Teilausdrücke behandelt.
Code-Beispiel in Haskell [Quelldatei]
Die beiden Gleichheitstest verdeutlichen die Strategie, wegen derer die nicht-strikte Auswertung
auch als "Call-by-need" bezeichnet wird. Die aktuellen Parameter sind nach Erzeugung eines
neuen Vektors mit der "
Beim ersten Aufruf wird diese Bedingung bereits bei Auswertung des ganz linken Ausdrucks verletzt.
Zunächst wird die Addition ausgewertet, ihr Ergebnis mit dem Wert 2 verglichen. Das Ergebnis ist
Im zweiten Beispiel liefert der erste Gleichheitstest
Code-Beispiel in Haskell [Quelldatei]
>> intlist :: Integer -> [Integer]
Aus der Erfahrung mit imperativen Programmiersprachen würde man vermuten,
dass diese Funktion nie terminiert. Dass dem nicht so ist, zeigen die beispielhaften
Aufrufe:
Durch "Lazy Evaluation" werden nie alle Elemente der Liste berechnet.
Ausgewertet werden nur die (rekursiven) Aufrufe von "2.1 "Lazy evaluation" oder "Call-by-need"
In "Haskell" werden Ausdrücke grundsätzlich nicht strikt ausgewertet. Ein (Teil-)Ausdruck wird
erst durch seinen Wert ersetzt, wenn dieser zum Beispiel für einen arithmetischen
Vergleich benötigt wird. Demzufolge wird auch ein als aktueller Parameter einer
Funktion übergebener Ausdruck erst ausgewertet, wenn sein Wert innerhalb des
Funktionsrumpfes verwendet wird. Wurde der Wert einmal berechnet,
werden alle namentlichen Vorkommen des Ausdrucks durch seinen Wert ersetzt.
Ein einfaches Beispiel verdeutlicht diese Strategie.
>> const1 a = 1
const1
" mit einem beliebigen Parameter "a
" und
dem Rückgabewert 1. Der Rückgabewert folgender beispielhafter Funktionsaufrufe ist also immer 1.
>> const1 10 => 1
>> const1 (1+4) => 1
>> const1 'A' => 1
>> const1 (1/0) => 1
2.2 Hintergrund
Definierte Teilausdrücke werden innerhalb von Ausdrücken oder als aktuelle
Parameter zunächst nur durch einen Namen identifiziert. Aufgrund der Verwendung
der Namen in Operationen muss der Interpreter entscheiden, ob der Wert des Ausdrucks
für das Ergebnis der Operation relevant ist. Zu diesem Zweck werden definierte Ausdrücke
auf Basis des Lambda-Kalküls zerlegt. Die resultierenden "Unbekannten" sind Bezeichner
für Teilausdrücke (Funktionen). Das folgende erweiterte Beispiel zeigt, dass die
Analyse des Umfeldes von Ausdrücken recht komplex werden kann.
2.3 Ein Anwendungsbeispiel
Als Beispiel wird einfache dreidimensionaler Vektortyp definiert. Für die Verarbeitung
werden Instanzen der Klasse "Eq" für den Gleichheitstest und der Klasse "Show" für
die String-Ausgabe an der Kommandozeile erzeugt.
>> data LVect3D = LVect3f {u,v,w::Float}
>> newvect u v w = LVect3f u v w
>> instance Show LVect3f
>> where show n = show(u n)++" "++show(v n)++" "++show(w n)
>> instance Eq LVect3f
>> where (==) a b = ((u a)==(u b)&&(v a)==(v b)&&(w a)==(w b))
>> (newvect (1+2) 2 4)==(newvect 2 (2/0) 4) => False
>> (newvect 1 2 4)==(newvect 1 (2/0) 4) => _|_
newvect
"-Methode zunächst Ausdrücke vom Type Float
mit den Namen u,v und w. Der Vergleich der Vektoren gegeneinander basiert auf
dem arithmetischen Gleichheitstest der einzelnen Koordinaten, also der Ausdrücke u,v,w.
Um diesen Test durchzuführen, müssen die Teilausdrücke ausgewertet werden. Das Ergebnis
des Tests ist durch die Und-Verknüpfung der Einzelvergleiche definiert, deren Teilausdrücke zu
True
ausgewertet werden müssen, um als Ergebnis True
zu erhalten.False
, womit der gesamte Test fehlgeschlagen ist. Die restlichen Ausdrücke werden nicht
berechnet.1==1 => True
. Folglich
ist der zweite Teilausdruck relevant für den Gesamtausdruck. Hier führt jedoch die Auswertung des
arithmetischen Ausdrucks 2/0
zu einem Laufzeitfehler, da das Ergebnis nicht definiert ist (_|_).2.4 Unendliche Strukturen
Mit Hilfe nicht-strikter Auswertung kann eine interessante Speicherstruktur realisiert werden, eine unendliche Liste.
Eine solche Liste kann in "Haskell" rekursiv erzeugt werden. Da funktionale Programme nicht linear
abgearbeitet werden, und mit Hilfe der "Lazy Evaluation", kann es dabei nie zu einer nicht terminierenden Rekursion kommen.
Folgende Funktion erzeugt eine unendliche Liste aus einem Integer-Anfangsausdruck, in dem sie zu dem jeweils eingefügten
aktuellen Ausdruck den Folgewert hinzufügt.
>> -- unendliche liste
>> intlist i = i : intlist (i+1)
>> zehnints = take 10 (intlist 0)
(Pseudocode)
>> von13bis21 = take 9 (intlist 13)
>> zehnints => [0,1,2,3,4,5,6,7,8,9]
>> von13bis21 => [13,14,15,16,17,18,19,20,21]intlist
", welche
zur Berechnung des geforderten Listenausschnitts benötigt werden (die Funktion "take x
"
liefert die ersten x Elemente einer Liste zurück).