Lecture 17 — 2015-11-04
Type classes
This lecture is written in literate Haskell; you can download the raw source.
What’s happening? We’ll do more weeks of serious Haskell training, and then… cool stuff! Topics will definitely include automatic random testing and parser combinators. We’ll choose between a few others: concurrency, lenses/bidirectional programming, applications of PL in networks, or other topics (like static analysis). I’ll put out a poll when the time comes.
For the upcoming HW08, it will come in two parts. The first part is up now; I’ll post to Piazza when the second part is available. It will be due on 2015-11-15.
Type classes
Type classes are groups of functions associated with a type. Each type can have a single instance for each type class.
What are they for? In general, type classes characterize common interfaces and behaviors. For example:
- printing (
Show) - parsing (
Read) - equality (
Eq) - ordering (
Ord) - representability in memory/disk (
Storable) 
Type classes get a lot of use in Haskell. More complex type classes include:
- enumerability (
Enum) - boundedness (
Bounded) - being a structure that has 
foldlandfoldr(Foldable) 
Type classes are very much like classes in object-oriented languages, but there are important differences. Java, C++, and Python use classes to group code and data together, but Haskell separates data definitions from class definitions. Traditional OO “objects” are instances of classes, but Haskell’s “objects” are just values, while “instances” are implementations of interfaces.
Running examples
We used three types as our running examples. TVL is a three-valued logic; List a is lists containing a; OneOfThree a b c is like Either a b but has a third option.
module Lec17 where
data TVL = Yes | No | Maybe
data List a =
    Nil
  | Cons a (List a)
data OneOfThree a b c =
    L a
  | M b
  | R cThe Show type class
Haskell declares Show as follows:
class Show a where
  show :: a -> String
Here are some instances that use it.
instance Show TVL where
  show Yes = "Yes"
  show No = "No"
  show Maybe = "Maybe"
instance (Show a) => Show (List a) where
  show Nil         = "Nil"
  show (Cons x xs) = show x ++ "," ++ show xs
  
instance (Show a, Show b, Show c) => Show (OneOfThree a b c) where
  show (L a) = "L " ++ show a
  show (M b) = "M " ++ show b
  show (R c) = "R " ++ show cWhen we want to use a type class function, we have to add a constraint to our type, as below. Pay careful attention to the difference between => and ->.
decision :: Show a => a -> String
decision result = "The committee answer was: " ++ show resultThe Eq and Ord type classes
The Eq type class is how we establish equality for Haskell types; the Ord type class is used for ordering. They are (effectively) defined as follows:
class Eq a where
  (==) : a -> a -> Bool
  (/=) : a -> a -> Bool
  a /= b = not (a == b)
                    
data Ordering = LT | EQ | GT
class Eq a => Ord a where
  compare :: a -> a -> Ordering
There are a few things to note here. First, note that Eq provides a default implementation for (/=), which just negates (==). We could have separately defined:
(/=) :: Eq a => a -> a -> Bool
a /= b = not (a == b)
The Haskell library designers included (/=) in the Eq type class with a default implementation, instead. Why? This way, someone can create an instance of Eq that defines a more efficient verison of (/=) if their data structure permits it.
The Ord type class has an Eq type class constraint. You can’t define an instance for Ord a unless there’s already an instance for Eq a.
As a first cut for TVL, we might write:
instance Eq TVL where
  No    == No    = True
  Maybe == Maybe = True
  Yes   == Yes   = True
  _     == _     = False
instance Ord TVL where
  compare No    No    = EQ
  compare Yes   Yes   = EQ
  compare Maybe Maybe = EQ
  compare No    _     = LT
  compare Maybe No    = GT
  compare _     Yes   = LT
We can do better though—there’s some redundancy here. If we define a single function for comparison, we can use it for both instances.
cmpTVL :: TVL -> TVL -> Ordering
cmpTVL No No = EQ
cmpTVL Maybe Maybe = EQ
cmpTVL Yes Yes = EQ
cmpTVL No _ = LT
cmpTVL Maybe No = GT
cmpTVL _ Yes = LT
instance Eq TVL where
  a == b = cmpTVL a b == EQ
instance Ord TVL where
  compare = cmpTVLnewtype and its uses
We’ve seen the data keyword, which introduces data structures that will exist at runtime. We’ve also seen the type keyword, which introduces a type synonym, a convenient shorthand for the type checker. The newtype keyword has features of both. Like type, it's just a hint for the type checker, and will have no influence on the runtime; likedata`, it has constructors… but only a single one.
The most common way to use a newtype is as follows:
newtype Reversed a = Reversed { reversedValue :: a }Note that (a) it takes a parameter, a, (b) the constructor has the same name as the type, and (c) we’ve used record notation to define an accessor, reversedValue.
So we have a constructor Reversed :: a -> Reversed a, an accessor or deconstructor reversedValiue :: Reversed a -> a… why bother? Recall that each type class can only have a single implementation of a given type class. Reversed a and a are different type, so we can give them different implementations. For example, here’s an implementation that reverses the ordering:
instance Show a => Show (Reversed a) where
  show (Reversed a) = "Reversed " ++ show a ++ ""
instance Eq a => Eq (Reversed a) where
  (Reversed a) == (Reversed b) = a == b
instance Ord a => Ord (Reversed a) where
  compare (Reversed a) (Reversed b) = 
    case compare a b of
      LT -> GT
      EQ -> EQ
      GT -> LTReversing the ordering is useful: this way sorting functions don’t need parameters to indicate direction, but can just follow the ordering of the type class.
Here’s another variation, where we suppress printing of values. This way we can print values but controllably hide the parts that might be too big or awkward to print on our console.
newtype Hidden a = Hidden { unhide :: a }
instance Show (Hidden a) where
  show _ = "Hidden _"
instance Eq a => Eq (Hidden a) where
  (Hidden a) == (Hidden b) = a == b
instance Ord a => Ord (Hidden a) where
  compare (Hidden a) (Hidden b) = compare a bKinds
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.
The type Mu we saw last week is even stranger:
newtype Mu f = Fold { unFold :: f (Mu f) }
Here, f has kind * -> *, and Mu f has kind *. So Mu itself has kind (* -> *) -> *. That is, Mu is a higher-kinded type: a type constructor that takes another type constructor.
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
Finally, we looked 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 nilEquality for functions on finite domains
Finally, we defined a notion of equality for function on finite domains.
instance (Eq a, Bounded a, Enum a, Eq b) => Eq (a -> b) where
  f1 == f2 = 
    let checkAt x = f1 x == f2 x in
    and $ map checkAt $ enumFromTo minBound maxBoundHaskell could do this in its standard library, but doesn’t… why not? Why might this be a bad idea?