Lecture 18 — 2015-11-09
Functor and Applicative
This lecture is written in literate Haskell; you can download the raw source.
A few words on abstraction and why we bother.
A lot of this material is borrowed/based on two resources written by Brent Yorgey: his Haskell course from Spring 2013 and his Typeclassopedia. I encourage you to go read these excellent resources!
{-# LANGUAGE FlexibleInstances, UndecidableInstances #-}
module Lec18 where
import Prelude hiding (map)
import Lec17
import Control.Applicative
import Data.Char
import System.Environment
import System.Exit
import qualified Data.Map as Map
import Data.Map (Map, (!))
map :: (a -> b) -> [a] -> [b]
map f = foldr ((:) . f) []
data BinTree a = BTEmpty | BTNode (BinTree a) a (BinTree a) deriving Show
treeMap f BTEmpty = BTEmpty
treeMap f (BTNode l v r) = BTNode (treeMap f l) (f v) (treeMap f r)
maybeMap f Nothing = Nothing
maybeMap f (Just x) = Just (f x)class Functor f where
fmap :: (a -> b) -> f a -> f b
Here f is a ‘container’, or, more broadly, a computational context.
instance Functor BinTree where
fmap = treeMap
add1 :: (Functor f, Num a) => f a -> f a
add1 = fmap (+1)What other instances can we find for Functor?
Well, the Listlike type class we saw might do…
instance Listlike f => Functor f where
fmap = each
…but this leads to undecidability. What if f already has its own instance of Functor? We can use newtype to resolve this issue.
newtype AsList f a = AsList { getList :: f a } deriving Show
instance Listlike f => Functor (AsList f) where
fmap f = AsList . each f . getList
l1 = add1 (AsList [1..10])
l2 = add1 (AsList (foldr cons nil [1..10] :: UnionTree Int))What about other types? Let’s try pairs.
instance Functor (,) where
fmap f (a,b) = (f a, f b)
…nope! The kinds aren’t even right… Functor applies to types of kind * -> *, but (,) has kind * -> * -> *. So we’ll need to apply (,) to a type to make it work.
instance Functor ((,) e) where
fmap f (a,b) = (a, f b)
Okay… why does the left stay fixed? Because here fmap : (a -> b) -> (e,a) -> (e,b).
We could, if we liked define pairs of values of the same type, as in:
data Pair a = Pair a a deriving Show
instance Functor Pair where
fmap f (Pair a b) = Pair (f a) (f b)Okay, so much for pairs. What about sum types?
instance Functor Either where
fmap f (Left a) = Left $ f a
fmap f (Right a) = Right $ f a
Hmm… nope. The kinds don’t check out. We can do this, though:
instance Functor (Either e) where
fmap f (Left e) = Left e
fmap f (Right a) = Right $ f a
Again, we choose the left side out of convenience. We could define something similar to Pair, if we wanted:
data Choice a = ColumnA a | ColumnB a
instance Functor Choice where
fmap f (ColumnA a) = ColumnA $ f a
fmap f (ColumnB a) = ColumnB $ f aAt first, I said that a type constructor f that is a Functor could be thought of as a container: fmap :: (a -> b) -> f a -> f b runs a function on the contents of a container. Thinking of functors as containers isn’t a bad intuition, but it’s slightly inadequate.
More broadly, a functor is a computational context, not just a container. That is, functors may not always be traversible the way that code is.
instance Functor ((->) e) where
fmap f g = \e -> f (g e)
That is:
instance Functor ((->) e) where
fmap = (.)
The Applicative type class
type Name = String
data Employee = Employee { name :: Name
, phone :: String }
deriving Show
maybeEmployee :: (Name -> String -> Employee) ->
(Maybe Name -> Maybe String -> Maybe Employee)
maybeEmployee f (Just n) (Just p) = Just $ f n p
maybeEmployee _ _ _ = Nothing
listEmployee :: (Name -> String -> Employee) ->
([Name] -> [String] -> [Employee])
listEmployee f n p = zipWith f n p
listEmployee' :: (Name -> String -> Employee) ->
([Name] -> [String] -> [Employee])
listEmployee' f n p = map (\(name,phone) -> f name phone) allPairs
where allPairs = Prelude.concat (map (\name -> map (\phone -> (name,phone)) p) n)
-- map (uncurry f) $ concatMap (\name -> map (\phone -> (name,phone)) p) n
funEmployee :: (Name -> String -> Employee) ->
(e -> Name) -> (e -> String) -> (e -> Employee)
funEmployee f mkName mkPhone = \e -> f (mkName e) (mkPhone e)Can we use Functor to do this? The type above looks like fmap, but with two arguments instead of one. Can we write a different function fmap2?
fmap2 :: Functor f => (a -> b -> c) -> f a -> f b -> f c
fmap2 f a b = undefined
where fa = fmap f a -- where do we go from here?Functor is a fine type class, but it only lets us operate opaquely. Once a function is trapped in Functor, we can’t get it out!
A different type class, Applicative, solves this problem.
class Functor f => Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b -- pronounced 'ap'
fmap2' :: Applicative f => (a -> b -> c) -> f a -> f b -> f c
fmap2' f a b = (fmap f a) <*> bpure :: a -> f a
fmap :: (a -> b) -> f a -> f b
fmap2 :: (a -> b -> c) -> f a -> f b -> f c
Note that to be an Applicative, you must be a Functor. Can we write fmap using the <*>?
fmap' :: Applicative f => (a -> b) -> f a -> f b
fmap' f a = pure f <*> aIn fact, using fmap with Applicatives is so common that we have the definition <$> = fmap.
fmap2Best :: Applicative f => (a -> b -> c) -> f a -> f b -> f c
fmap2Best f a b = f <$> a <*> b -- note left associativityFinally, let’s note that this function is defined as liftA2 in the module Control.Applicative.
Let’s write some Applicative instances and work with it more.
instance Applicative Pair where
pure a = Pair a a
(Pair f1 f2) <*> (Pair a1 a2) = Pair (f1 a1) (f2 a2)
instance Applicative Choice where
pure a = ColumnA a
ColumnA f <*> a = fmap f a
ColumnB f <*> a = fmap f aHow else could we have done it? Well, it turns out there are laws that should hold for Functor and Applicative. In particular:
fmap id == id fmap (f . g) == fmap f . fmap g fmap f x === pure f <*> x
instance Applicative BinTree where
pure a = BTNode BTEmpty a BTEmpty
BTEmpty <*> _ = BTEmpty
BTNode _ _ _ <*> BTEmpty = BTEmpty
BTNode lf f rf <*> BTNode la a ra = BTNode (lf <*> la) (f a) (rf <*> ra)
Does that law hold for the above? No, not considering what we defined as Functor BinTree above! We would need something like:
instance Applicative BinTree where
pure a = BTNode BTEmpty a BTEmpty
BTEmpty <*> _ = BTEmpty
BTNode _ f _ <*> t = treeMap f tLet’s look at the Applicative instance for Maybe.
instance Applicative Maybe where
pure = Just
Nothing <*> _ = Nothing
_ <*> Nothing = Nothing
Just f <*> Just x = Just (f x)
m_name1, m_name2 :: Maybe Name
m_name1 = Nothing
m_name2 = Just "Brent"
m_phone1, m_phone2 :: Maybe String
m_phone1 = Nothing
m_phone2 = Just "555-1234"
ex01 = Employee <$> m_name1 <*> m_phone1
ex02 = Employee <$> m_name1 <*> m_phone2
ex03 = Employee <$> m_name2 <*> m_phone1
ex04 = Employee <$> m_name2 <*> m_phone2(.+) = liftA2 (+)
(.*) = liftA2 (*)n = ([10,5] .* [4,8]) .+ [3,4] .+ [4,3]newtype ZipList a = ZipList { getZipList :: [a] }
deriving (Eq, Show, Functor)
instance Applicative ZipList where
pure = ZipList . repeat
ZipList fs <*> ZipList xs = ZipList (zipWith ($) fs xs)
Now we can see a couple of different ways to go back and redefine Applicative BinTree. Should it expand into “tree product”, or should it try to match up trees (in which case we need something like repeat for trees)?