#include "head.html"

Interpreter-Monaden

Im bisherigen Beispiel war der Wert eines Ausdrucks eine Zahl, und die Operatoren standen für Funktionen :: Integer -> Integer -> Integer. Wir können das aber völlig generisch halten: erstens müssen es nicht immer Zahlen sein, sondern irgendein Resultattyp a. Zweitens möchten wir auch modellieren, daß Rechnungen (unsere Ausdrücke beschreiben Rechnungen) eventuell kein oder mehrer Resultate haben, aber alle vom Typ a. Wir können hier eine Monade m verwenden, die Instanz von MonadZero und MonadPlus ist.
#include "../src/Monad_Let_Eval.hs"
Wir sehen, daß die monadische Notation kaum noch Änderungen erfordert. Wir bemerken die Hilfsfunktion mapM mit der Definition
#include "../src/MapM.hs"
Diese schaltet mehrere Rechnungen hintereinander. Die jeweils nächste ignoriert die Ergebnisse der vorigen. Die "Nebenwirkungen" natürlich nicht, die werden ja durch die Reihenfolge des "do" geordnet. Beispiel:
putStr = mapM putChar
Übung: berechne (erst auf dem Papier, dann mit hugs)
mapM ( \ x -> [x, x+1]) [2,4,6]

Benutzung des monadischen Interpreters

Was können wir mit dem Hauptprogramm
#include "../src/Monad_Let_Main.hs"
anstellen? Wir bemerken wieder, daß es nur einen Unterschied zu früher (nicht-monadisch) gibt: an eval_top t steht ein Typ dran. Diese Deklaration legt fest, in welcher Monade wir rechnen. (Übung: entferne die Deklaration und kompiliere.)

#include "../src/Meaning.hs"

Wir benutzen Maybe für eventuell fehlschlagende Rechnungen. Testeingabe: eval_read "8 / 0"

Was man sich wünschen könnte, wäre eine genauere Information im Fehlerfall, also statt Maybe gerne der Fehler-Typ des Parsers, der ja auch die Fehlerstelle im Quelltext angeben kann. Allerdings ist zur Zeit der Auswertung der Quelltext längst verschwunden. Man sieht, daß es sich trotzdem lohnt, Teile des Quelltextes, oder wenigestens Zeilennummern, im Syntaxbaum aufzuheben. (Das machen ja die klassischen Compiler auch, bei gcc -c -g werden Zeilennummern einkompiliert, damit der debugger sich dann besser zurechtfindet.)

Oder wir nehmen List für verzweigende Rechnungen. Eine solche geben wir durch den Oder-Strich ein. Testeingaben:

read_eval "3 * (4 | 5)"
Bei unserer Definition haben wir
read_eval "(3 | 4) * (3 | 4)"        ==> [9, 12, 12, 16]
read_eval "let {x = 3 | 4} in x * x" ==> [9, 16]
Wir haben es nämlich so eingerichtet, daß Variablen an Zahlen gebunden werden. Dabei gilt, wie wir sehen, die eigentlich erwartete Äquivalenz der beiden Ausdrücke nicht.

Man hätte es sich auch anders wünschen können: Variablen werden an Rechnungen gebunden (d. h. hier Resultatlisten), und bei jeder Benutzung (nicht nur bei jeder Bindung) kann jeder Wert drankommen, weil die Berechnung "neu ausgeführt" wird.

Rechnet man bei jeder Benutzung, ist man vielleicht flexibler. Rechnet man nur einmal und bindet den Wert, geht das schneller (vorausgesetzt, man braucht den Wert überhaupt.)

Genau dieses Tradeoff habe ich auch beim Musik-Sequenzer berücksichtigt. Es gibt dort in der Sprache Bindungen mit "=" (Rechnung) und mit "==" (Wert). Den Unterschied bemerkt man, wenn man "x = ask random" und "x == ask random" vergleicht. Das erste x ist bei jeder Benutzung ein anderes, das zweite ist bei jeder Benutzung das gleiche. Siehe dieses Beispiel. Dort steht x == ask random, weil wir den einmal gewürfelten Wert (Ton) in Folge oft benutzen wollen.

Zur Implementierung suche nach Let und LetEval im Quelltext. #include "foot.html"