"Lazy evaluation"


[Inhalt]...[Strikte Auswertung]...[Vor-/Nachteile]


4. "Strictness" in Haskell


4.1 Erzwungene Auswertung

Die Auswertung von Teilausdrücken muss aufgrund der "Lazy evaluation" in "Haskell" unter Umständen erzwungen werden ("Force strictness"). Da standardmäßig nicht strikt ausgewertet wird, steht hierfür in "Haskell" die Funktion "seq" zur Verfügung.

>> seq :: a->(b->b)

Die Funktion erwartet als Parameter einen Ausdruck a und einen Funktionsausdruck (b->b), der auf a angewendet werden soll. Laut dem "Haskell 98 Report" ist ihr Wert folgendermaßen definiert:

>> seq _|_ f = _|_
>> seq x f = f x

(Pseudocode)

Das Zeichen "_|_" steht für einen nicht terminierenden Ausdruck (wie die Division durch null). Ist also der Ausdruck, auf den die Funktion angewendet werden soll, nicht definiert, so ist auch das Ergebnis der "seq" -Funktion undefiniert. Andernfalls ist das Ergebnis abhängig von dem Ausdruck (b->b). Die "seq"-Funktion kann als normale Funktion seq x (f x) oder als Infix-Operator x `seq` f x benutzt werden. Bei zusammengesetzten Datentypen muss dabei beachtet werden, dass "seq" nur die jeweils erste Ebene eines Ausdrucks (also z.B. bei einer Liste den Kopf) auswertet.
Für geschachtelte Ausdrücke kann zudem der Alias "$!" verwendet werden:

>> $! :: (a->b) -> a -> b
>> f $! x = x 'seq' f x

4.2 Simulation von "Call-by-value"

Mit Hilfe der "seq"-Methode kann in "Haskell" das Prinzip von "Call-by-value" simuliert werden. Zu Demonstrationszwecken soll noch einmal das Code-Beispiel aus Kapitel 2 "Nicht-strikte Auswertung" herangezogen werden. Ein neuer Typ mit einer veränderten Konstruktor-Funktion, welche die aktuellen Parameter strikt auswertet, soll implementiert werden.

Code-Beispiel in Haskell [Quelldatei]

>> data SVect3D = SVect3f {u,v,w::Float}
>> newsvect x y z = ((SVect3f $! x) $! y) $! z

Die "Eq"-/ "Show"-Instanzen bleiben unverändert.

>> instance Show SVect3f
>>     where show n = show(u n)++" "++show(v n)++" "++show(w n)

>> instance Eq SVect3f
>>     where (==) a b = ((u a)==(u b)&&(v a)==(v b)&&(w a)==(w b))

Durch die Verwendung des "$!"-Operators werden bei Erzeugen eines neuen Ausdrucks vom Typ "SVect3f" alle drei übergebenen Float-Audrücke ausgewertet. Ein Vergleich, wie er in Kapitel 2 mit "Lazy Evaluation" ausgewertet wurde, schlägt nun fehl:

>> (newsvect (1+2) 2 4)==(newsvect 2 (2/0) 4) => _|_

Dieser Gleichheitstest führt zu einem Laufzeitfehler, weil der Ausdruck (2/0) bei Konstruktion des (zweiten) Vektors strikt ausgewertet wird.

4.3 "Strictness flags"

Das Beispiel für die Simulation von "Call-by-value" zeigt einen typischen Fall, in dem eine strikte Auswertung von Teilausdrücken gewünscht ist. Es kann sinnvoll sein, sicherzustellen, dass bei Konstruktion eines neuen Ausdruck eines bestimmten Typs alle Teilausdrücke terminieren (definiert sind). Für diesen Sonderfall existiert in "Haskell" ein weiteres syntaktisches Element, mit dem Parameter von Typ-Konstruktoren als strikt gekennzeichnet werden. Ein Ausrufezeichen vor der Typangabe des formellen Parameters im Konstruktor erfüllt diesen Zweck. Mit Hilfe dieser "strictness flags" kann der "Call-by-value"-Konstruktor abgekürzt implementiert werden. Dazu noch einmal das Beispiel mit dem Vektor-Typ:

Code-Beispiel in Haskell [Quelldatei]

>> data SFVect3D = SFVect3f !Float !Float !Float
>> newsfvect f g h = SFVect3f f g h

Das Verhalten entspricht dem "Call-by-value"-Beispiel.


[Inhalt]...[Strikte Auswertung]...[Vor-/Nachteile]