Lecture 6.0 — 2017-02-06

Type classes

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

We revisited our Listlike type class…

class Listlike f where
  nil :: f a

  cons :: a -> f a -> f 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
  foldRight f b l =
    case openCons l of
      Nothing -> b
      Just (x,xs) -> f x (foldRight f b xs)

  foldLeft :: (b -> a -> b) -> b -> f a -> b
  foldLeft f b l =
    case openCons l of
      Nothing -> b
      Just (x, xs) -> foldLeft f (f b x) xs

  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 xs

…and its implementation using lists…

instance Listlike ([]) where
  -- nil :: [a]
  nil = []
  
  -- cons :: a -> [a] -> [a]
  -- cons x xs = x:xs
  cons = (:)
  
  -- openCons :: [a] -> Maybe (a,[a])
  openCons [] = Nothing
  openCons (x:xs) = Just (x,xs)
  
myConcat :: Listlike f => f (f a) -> f a
myConcat = foldRight append nil

But then we looked at an alternative implementation using union trees (a similar data structure is used in the union-find algorithm:

data UnionTree a =
    Empty -- []
  | Singleton a -- [a]
  | Union { left :: UnionTree a, right :: UnionTree a }
    deriving Show
    
instance Listlike UnionTree where
  -- nil :: f a
  nil = Empty

  -- cons :: a -> f a -> f a
  cons x xs = Union (Singleton x) xs
  
  -- openCons :: f a -> Maybe (a,f a)
  openCons Empty = Nothing
  openCons (Singleton x) = Just (x,Empty)
  openCons (Union l r) = 
    case openCons l of
      Nothing -> openCons r
      Just (x,l') -> Just (x,Union l' r)

ut1,ut2,ut3 :: UnionTree String
ut1 = Union (Singleton "hi") (Singleton "there")
ut2 = Union Empty Empty
ut3 = Union (Singleton "everybody") Empty

Observe that the same myConcat function applies just as well to our union trees as they did to lists. You can think of type classes as being implicit parameters that get automatically filled in by Haskell.

ut = myConcat (Union (Singleton ut1) (Union (Singleton ut2) (Singleton ut3)))

We defined a few more:

toList :: Listlike f => f a -> [a]
toList l = foldRight (:) [] l

fromList :: Listlike f => [a] -> f a
fromList l = foldr cons nil l

Embarrassingly, I tried to show how Haskell sometimes can’t figure out which typeclass to use… but it turns out it will default to list when it’s available. We will, however, eventually find cases where Haskell can’t figure out which type class to use—in which case you’ll need to use a type annotation.