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.