Lecture 8.0 — 2017-02-13

Functor; WhileNZ in code

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

After reviewing homework, we went over one final Functor instance: that for (->) r, the so-called reader. Note that (->) r :: * -> *: we’ve already supplied the domain type, r, but we still need to find out the codomain type.

instance Functor ((->) r) where
  -- fmap :: (a -> b) -> (r -> a) -> (r -> b)
  fmap fab fra r = fab (fra r)
  -- OR: fmap = (.)

Tricky things to note:

  1. writing (->) turns the infix -> operator into a standard prefix operator;
  2. writing (->) r is just like writing r -> __, where the blank is waiting to be filled;
  3. the way to find out the type of a type class method is textual substitution

A simple interpreter

We spent the last bit 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 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 returned to our old friend, ArithExp, and added a notion of variable, like we did on the board on Wednesday.

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 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.

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

We 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 tried 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 ran them in a sample store:

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

Testing our program found a bug—we forgot to do any actual looping at first!