SOE ex.7.5


data Expr
  =  C Float
  |  Expr :+ Expr
  |  Expr :- Expr
  |  Expr :* Expr
  |  Expr :/ Expr
  |  Let Var Expr Expr
  |  V Var

type Var = String
type VarList = [(Var, Float)]

evaluate :: Expr -> Float
evaluate = eval []

eval :: VarList -> Expr -> Float
eval vars (C x) = x
eval vars (e1 :+ e2) = eval vars e1 + eval vars e2
eval vars (e1 :- e2) = eval vars e1 - eval vars e2
eval vars (e1 :* e2) = eval vars e1 * eval vars e2
eval vars (e1 :/ e2) = eval vars e1 / eval vars e2
eval vars (V var)    = getVariable vars var
eval vars (Let var e1 e2) = eval vars' e2
  where vars' = setVariable vars var (eval vars e1)

getVariable :: VarList -> Var -> Float
getVariable [] varName = error ("Undefine variable: " ++ varName)
getVariable ((name,value):vs) varName
  | name == varName  = value
  | otherwise        = getVariable vs varName

setVariable :: VarList -> Var -> Float -> VarList
setVariable vars varName varValue = (varName, varValue) : vars

----- some tests -----

result1 = evaluate (Let "x" (C 5) (V "x" :+ V "x"))

{- 
  The way definied getVariable/setVariable will respect variable's scope!
  Let's try something more complex to see it:

  let x=10 in x+(let x=(x*2) in (let y=4 in y-x)*2) ===> -22.0
-}

result2 = evaluate
  (
    Let "x" (C 10)
      ( V "x" :+
          Let "x" (V "x" :* (C 2))
             ((Let "y" (C 4) (V "y" :- V "x")) :* (C 2))
      )
  )

main = do
  putStrLn $ show $ result1
  putStrLn $ show $ result2
  putStrLn $ show $ evaluate ( V "y" )  -- error

Leave a Reply