module Monad_Let_Eval where
import Let_Type
import Let_Form
import Env
import Meaning
import MonadZero
import MonadPlus
import Prelude hiding (mapM)
import MapM
eval :: (MonadZero m, MonadPlus m)
=> Envs Integer -> Term -> m Integer
eval envs t = case t of
Constant n -> return n
Apply o l r -> do let [mx, my] = map (eval envs) [l, r]
let f = meaning o
x <- mx; y <- my
f x y
Var n -> case finds envs n of
Nothing -> error $ "Variable " ++ n ++ " nicht gebunden"
Just x -> return x
Let binds body -> do
nxs <- mapM ( \ (n, t) -> do x <- eval envs t
return (n, x) )
binds
eval (mkEnv nxs : envs) body
eval_top :: (MonadZero m, MonadPlus m)
=> Term -> m Integer
eval_top = eval nullEnvs
Wir sehen, daß die monadische Notation kaum noch Änderungen erfordert.
Wir bemerken die Hilfsfunktion mapM mit der Definition
module MapM where import Prelude hiding ( mapM ) mapM :: Monad m => (a -> m b) -> [a] -> m [b] mapM f [] = return [] mapM f (x : xs) = do y <- f x; ys <- mapM f xs; return $ y : ysDiese 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]
module Monad_Let_Main where
import Let_Type
import Let_Parser
import Let_Form
import Monad_Let_Eval
import MaybeMonadPlus
import ListMonadPlus
read_eval :: String -> String
read_eval input =
case parse (final term) input of
Left msg -> msg
Right t -> let x = eval_top t :: [ Integer ]
in show x
main = do
input <- getContents
putStrLn $ read_eval input
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.)
module Meaning where import MonadZero import MonadPlus import Op meaning :: (MonadZero m, MonadPlus m) => Op -> (Integer -> Integer -> m Integer) meaning Plus x y = return ( x + y ) meaning Minus x y = return ( x - y ) meaning Mal x y = return ( x * y ) meaning Durch x y = if 0 == y then zero else return (x `div` y) meaning Oder x y = return x `plus` return y
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.