Lecture 8.0 — 2016-09-22
Quiz; Functor; interpreters for WhileNZ
in Haskell
This lecture is written in literate Haskell; you can download the raw source.
{-# OPTIONS_GHC -W -fno-warn-unused-imports #-}
We spent most of the class talking about how to write an interpreter for our simple language consisting of arithmetic expressions, assignments, and loops.
The first order of business was thinking about variables: how should we represent them? Haskell’s map type seemed like a good bet.
import Data.Map (Map, (!))
import qualified Data.Map as Map
type VarName = String
type Store = Map VarName Int
We talked briefly about an alternative: we can use the original maps, functions. We went so far as to write a definition or two:
type Store = VarName -> Int
update :: VarName -> Int -> Store -> Store
update x n s = \y -> if x == y then n else s y
But then we fell off, because functions have a big problem: it’s not clear how to print them out.
Arithmetic expressions
We returned to our old friend, ArithExp
, and added a notion of variable.
data ArithExp =
Var VarName
| Num Int
| Plus ArithExp ArithExp
| Times ArithExp ArithExp
| Neg ArithExp
The interpreter was pretty similar to our old one, but note that we take an extra argument, st :: Store
. The store is where we look for variables. If a variable isn’t in the store, then Map.!
will complain with an error.
evalA :: Store -> ArithExp -> Int
evalA st (Var x) = st ! x
evalA _ (Num n) = n
evalA st (Plus e1 e2) = (evalA st e1) + (evalA st e2)
evalA st (Times e1 e2) = (evalA st e1) * (evalA st e2)
evalA st (Neg e) = negate $ evalA st e
Statements in WhileNZ
Having converted the mathematical grammer:
s ::= skip | x := a | s1;s2 | while a s
To a Haskell datatype:
data WhileNZ =
Skip
| Assign VarName ArithExp
| Seq WhileNZ WhileNZ
| WhileNZ ArithExp WhileNZ
We set about writing our interpreter. While evalA :: Store -> ArithExp -> Int
, here interp
takes a Store
and a statement and returns a new store.
interp :: Store -> WhileNZ -> Store
interp st Skip = st
interp st (Assign x a) = Map.insert x (evalA st a) st
Some students worried that the line above failed to account for side-effects that a :: ArithExp
might have. But the type of evalA
shows that there can be none: it just returns an Int
, not a new Store
. Reflect for a moment: we looked at the type and were able to deduce the absence of side effects. Incredible!
interp st (Seq s1 s2) = interp (interp st s1) s2
interp st sLoop@(WhileNZ a s) =
if evalA st a == 0
then st
else interp (interp st s) sLoop
Finally, we were able to run a few programs.
s3 = Seq (Assign "x" (Num 1))
(Seq (Assign "y" (Num 2))
(Assign "z" (Plus (Var "x") (Var "y"))))
upTo n = Seq (Assign "x" (Num n))
(Seq (Assign "y" (Num 0))
(WhileNZ (Plus (Var "x") (Neg (Var "y")))
(Assign "y" (Plus (Var "y") (Num 1)))))
loop = WhileNZ (Num 1) Skip
Having been bitten by an infinite loop, we wrote a simple “static analysis” (a thing that looks at program without running it) that determines whether the program will halt. Our analysis was pretty coarse: if a program has no loops, it halts.
noWhiles :: WhileNZ -> Bool
noWhiles Skip = True
noWhiles (Assign _ _) = True
noWhiles (Seq s1 s2) = noWhiles s1 && noWhiles s2
noWhiles (WhileNZ _ _) = False
We concluded class by talking briefly about Functor
for the final time. We saw, in particular, the functor instance for “readers” (->) r
:
instance Functor ((->) r) where
fmap = (.)
To convince yourself this is right, write down the type of fmap
for this instance.