Lecture 4.0 — 2016-09-08
Our first interpreter; type classes
This lecture is written in literate Haskell; you can download the raw source.
After looking at the language likes and dislikes, we saw some examples from the homework and did some refactoring as a class.
Then I introduced us to our first interpreter. We started on the board, defining a grammar like so:
p, q in Prop ::= T | ⊥ | p ∧ q | p ∨ q | ¬ p | p ⇒ q
p1 = T ∧ ⊥
p2 = ⊥ ∨ ⊥
We talked about what the propositions p1 and p2 mean… nothing, yet! We had to define an interpretation function, where we interpret elements of Prop as booleans:
2 = { true, false} with standard operations &&, ||, and !
interp : Prop -> 2
interp(T) = true
interp(⊥) = false
interp(p ∧ q) = interp(p) && interp(q)
interp(p ∨ q) = interp(p) || interp(q)
interp(¬ p) = !interp(p)
interp(p ⇒ q) = !interp(p) || interp(q)
Then we did the same in Haskell:
data Prop =
T
| F
| And Prop Prop
| Or Prop Prop
| Not Prop
| Implies Prop Prop
p1 :: Prop
p1 = T `And` F
interp :: Prop -> Bool
interp T = True
interp F = False
interp (And p q) = interp p && interp q
interp (Or p q) = interp p || interp q
interp (Not p) = not $ interp p
interp (Implies p q) = not (interp p) || interp qWe wrote a “pretty printer” for Prop which prints out appropriately parenthesized concrete syntax. It was a little aggressive, though, printing way too many parentheses:
parens :: String -> String
parens s = "(" ++ s ++ ")"
instance Show Prop where
show T = "T"
show F = "_|_"
show (And p q) =
parens (show p) ++ " /\\ " ++ parens (show q)
show (Or p q) =
parens (show p) ++ " \\/ " ++ parens (show q)
show (Not p) = "~" ++ parens (show p)
show (Implies p q) =
parens (show p) ++ " => " ++ parens (show q)Finally, we talked about how we can manipulte the abstract syntax of Prop in its own right; for example, we can translate away any use of Implies.
compile :: Prop -> Prop
compile (Implies p q) = Not (compile p) `Or` compile q
compile (And p q) = And (compile p) (compile q)
compile (Or p q) = Or (compile p) (compile q)
compile (Not p) = Not $ compile p
compile T = T
compile F = F
Kinds; Listlike as a typeclass
Kinds
Just as types classify terms, kinds classify types. Haskell has two kinds:
k ::= * | * -> *
First, *, pronounced “star”, is the kind of complete types, which classify terms. Int has kind *, as does Bool. The types [Int] and Maybe Bool have kind *, too. The types [] (read “list”) or Maybe have kind * -> *: they’re type constructors. There are no terms with the type [] or Maybe… terms only ever have types of kind *.
But: if you give [] a type, then you will have a complete type, as in [Int].
Next, the type of functions (->) has the kind * -> * -> *. If you give (->) two type parameters a and b, you will have a function type, a -> b. If you give it just one parameter, you will have a type constructor (a ->) of kind * -> *. It is unfortunately somewhat confusing that -> means two different things here: it’s both the function type and the ‘arrow’ kind. Rough stuff.
Just as :t in GHCi will tell you the type of a term, :k will tell you the type of a kind. For example:
GHCi, version 7.10.2: http://www.haskell.org/ghc/ :? for help
Prelude> :k []
[] :: * -> *
Prelude>
Why do we care about kinds? In the next few classes, we’ll be looking at non-* kinded types in order to talk about groups of behavior. The next bit will be a first foray into types with interesting kinds.
Interface-like typeclasses
We didn’t get a chance to look at type classes that characterize behavior. The Listlike type class characterizes type constructors of kind * -> * that behave like lists.
class Listlike f where
nil :: f a
cons :: a -> f a -> f aNote that f must have kind * -> *, because we apply it to the type parameter a, which must have kind *. Why? Because cons has type a -> f a -> f a, and (->) has kind * -> * -> * and is applied to a.
openCons :: f a -> Maybe (a,f a)
hd :: f a -> Maybe a
hd l =
case openCons l of
Nothing -> Nothing
Just (x,_) -> Just x
tl :: f a -> Maybe (f a)
tl l =
case openCons l of
Nothing -> Nothing
Just (_,xs) -> Just xs
isNil :: f a -> Bool
isNil l =
case openCons l of
Nothing -> True
Just _ -> False
foldRight :: (a -> b -> b) -> b -> f a -> b
foldLeft :: (b -> a -> b) -> b -> f a -> b
each :: (a -> b) -> f a -> f b
each f = foldRight (cons . f) nil
append :: f a -> f a -> f a
append xs ys = foldRight cons ys xsWe can show that the list type constructor, [], which has kind * -> *, is an instance of the Listlike class. On an intuitive level, this should be no surprise: lists are indeed listlike.
instance Listlike [] where
nil = []
cons = (:)
openCons [] = Nothing
openCons (x:xs) = Just (x,xs)
tl [] = Nothing
tl (_:xs) = Just xs
isNil = null
foldRight = foldr
foldLeft = foldl
-- just take each and append as the usualWe also defined a union-tree as an alternate list representation.
data UnionTree a =
Empty
| Singleton a
| Union { left :: (UnionTree a), right :: (UnionTree a) }
deriving Show
instance Listlike UnionTree where
nil = Empty
cons x Empty = Singleton x
cons x xs = Union (Singleton x) xs
openCons Empty = Nothing
openCons (Singleton a) = Just (a,Empty)
openCons (Union l r) =
case openCons l of
Nothing -> openCons r
Just (x,l') -> Just (x,Union l' r)
isNil Empty = True
isNil (Singleton _) = False
isNil (Union l r) = isNil l && isNil r
foldRight f v l =
case openCons l of
Nothing -> v
Just (x,xs) -> f x (foldRight f v xs)
foldLeft f v l =
case openCons l of
Nothing -> v
Just (x,xs) -> foldLeft f (f v x) xs
each f Empty = Empty
each f (Singleton a) = Singleton $ f a
each f (Union l r) = Union (each f l) (each f r)
append = UnionNote that we overload append to use a much more efficient implementation—O(1) compared to O(n)!
asList :: Listlike f => f a -> [a]
asList = foldRight (:) []
concat :: Listlike f => f (f a) -> f a
concat = foldRight append nil