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 a
At 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) <*> b
pure :: 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 <*> a
In fact, using fmap
with Applicative
s 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 associativity
Finally, 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 a
How 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 t
Let’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)?