Lecture 6 — 2020-02-04

WhileNZ

This lecture is written in literate Haskell; you can download the raw source.

We started defining a simple imperative language, which we called WhileNZ. Here’s its syntax and semantics:

n in Nat
v in Var (some set of variables)
e in Expr    ::= n | e1 plus e2 | e1 times e2 | neg e | x
c in Command ::= SKIP | c1 ; c2 | x := e | WHILENZ e DO c END

sigma in Store, the set of total functions assigning ints to variables
                i.e. Var -> Z

(sigma[x |-> z])(y) = / z           if x = y
                      \ sigma(y)    otherwise
                   
eval : Store -> Expr -> Z
eval(sigma, n)           = n
eval(sigma, e1 plus  e2) = eval(sigma, e1) + eval(sigma, e2)
eval(sigma, e1 times e2) = eval(sigma, e1) * eval(sigma, e2)
eval(sigma, neg e)       = -eval(sigma, e)
eval(sigma, x)           = sigma(x)

interp : Store -> Command -> Store
interp(sigma, SKIP)    = sigma
interp(sigma, c1 ; c2) = interp(interp(sigma, c1), c2)
interp(sigma, x := e)  = sigma[x|->eval(sigma, e)]
interp(sigma, WHILENZ e DO c END) = 
  if eval(sigma, e) = 0
  then sigma
  else interp(interp(sigma, c), WHILENZ e DO c)

We went over an example or two in class, running some loops and observing that sometimes interp wasn’t well defined, i.e., sometimes it ran forever, which isn’t the sort of thing that math is supposed to do.

Try running interp on the following programs… what do they do? Do they always terminate? If so, what values will you get out?

Y := 5;
X := 1;
WHILENZ Y DO
  X := Y * X;
  Y := Y - 1
END

What will X be? What about Y?

A := X;
WHILENZ A DO
  B := B + 1
END

What values will A, B and X have?

X := 1;
WHILENZ X DO
  X := X + 1
END

A simple interpreter

Here’s how to write an interpreter for our simple language consisting of arithmetic expressions, assignments, and loops.

The first order of business is thinking about variables: how should we represent them? Haskell’s map type seems like a good bet.

import qualified Data.Map as Map
import Data.Map (Map, (!))

type VarName = String

type Store = Map VarName Int

update :: VarName -> Int -> Store -> Store
update v n st = Map.insert v n st

Arithmetic expressions

We return to our old friend, ArithExp, adding a notion of variable.

data ArithExp =
    Num Int
  | Var VarName
  | Plus ArithExp ArithExp
  | Times ArithExp ArithExp
  | Neg ArithExp
  deriving (Show, Eq)
           
evalA :: Store -> ArithExp -> Int
evalA st (Var x) = st ! x -- Map.findWithDefault ?
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) = - (evalA st e)

The interpreter is 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.

Commands in WhileNZ

Having converted the mathematical grammar:

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
  deriving Show

Let’s set about writing our interpreter. While evalA :: Store -> ArithExp -> Int, here eval takes a Store and a statement and returns a new store.

eval :: Store -> WhileNZ -> Store
eval st Skip = st
eval st (Assign x e) = update x (evalA st e) st
eval st (Seq c1 c2) = eval (eval st c1) c2
eval st eLoop@(WhileNZ e c) = 
  if evalA st e == 0
  then st
  else eval (eval st c) eLoop

We can try writing a program or two…

p1, p2 :: WhileNZ
p1 = Seq (Assign "x" (Plus (Var "x") (Num 1)))
         (Assign "y" (Plus (Var "y") (Num (-1))))
p2 = Seq (Assign "y" (Num 5))
     (Seq (Assign "x" (Num 0))
          (WhileNZ (Var "y") p1))

…and then we can run them in a sample store:

stXY :: Store
stXY = Map.fromList [("x",0),("y",0)]