Functors
class Functor_ (t :: * -> *) where
map :: (a -> b) -> t a -> t b
Two laws:
map id = id
map (f . g) = map f . map g
Maybe
Recall mapMaybe
from before:
instance Functor_ Maybe where
-- map :: (a -> b) -> Maybe a -> Maybe b
map f Nothing = Nothing
map f (Just x) = Just $ f x
Lists
instance Functor_ [] where
-- map :: (a -> b) -> [] a -> [] b
-- map :: (a -> b) -> [a] -> [b]
map = Prelude.map
Pairs
The pair constructor (,)
has kind * -> * -> *
. So, to be a
Functor_
, need to partially apply it to one type.
instance Functor_ ((,) a) where
-- map :: (b -> b') -> (,) a b -> (,) a b'
-- map :: (b -> b') -> (a, b) -> (a, b')
map f (a, b) = (a, f b)
Either
data Either a b = Left a | Right b
A very general mapping function would take a function for each variant:
mapEither :: (a -> a') -> (b -> b') -> Either a b -> Either a' b'
mapEither f g (Left a) = Left $ f a
mapEither f g (Right b) = Right $ g b
But the type of this function doesn't fit the requirements
of Functor_.map
. Like (,)
, need to partially apply
Either
to one type. So, must implement "mapRight
":
instance Functor_ (Either a) where
-- map :: (b -> b') -> Either a b -> Either a b'
map f (Left a) = Left a
map f (Right b) = Right $ f b
This is still useful: transform only Right
values while
simply propagating Left
values (often used to represent
errors).
Note that the following seemingly equivalent definition
(notice the Left
equation defined in terms of la
)
does not typecheck because Haskell cannot assign two
types (Either a b
and Either a b'
) to the expression
la
:
instance Functor_ (Either a) where
-- map :: (b -> b') -> Either a b -> Either a b'
map f (Right b) = Right $ f b
map f la = la
Functions
instance Functor_ ((->) t) where
-- map :: (a -> b) -> ((->) t) a -> ((->) t) b
-- map :: (a -> b) -> (t -> a) -> (t -> b)
-- map f g = \t -> f (g t)
-- map f g = f . g
map = (.)
Association Lists
type AssocList k v = [(k, v)]
type AssocList k v = [] (k, v)
type AssocList k v = [] ((,) k v)
Haskell's language of types provides (curried) type application
(including partial application), but there aren't enough
type-level computational features to define a
"type-level lambda" equivalent to "\v -> [] ((,) k v)
". So
we can't create an instance definition for AssocList
directly
(via the []
and (,)
and types). Neither can we via the
name AssocList
, because it is an alias rather than a
"real" type. Instances cannot be declaried via aliases to
help enforce the one-type, one-instance restriction.
Thus, we must create a wrapper type. (Recall that we often
will use newtype
, rather than data
, to define datatypes
with exactly one one-argument constructor.)
newtype AssocList k v =
BoxAssocList [(k, v)]
AssocList
is a Functor_
via a combination of map
for
lists and map
for pairs:
instance Functor_ (AssocList k) where
-- map :: (a -> b) -> (AssocList k) a -> (AssocList k) b
-- map :: (a -> b) -> AssocList k a -> AssocList k b
-- map f (BoxAssocList kvs) = BoxAssocList $ Prelude.map (Functor_.map f) kvs
map f (BoxAssocList kvs) = BoxAssocList $ Functor_.map (Functor_.map f) kvs
If we go back and add record type syntax...
newtype AssocList k v =
BoxAssocList { unboxAssocList :: [(k, v)] }
... we can simplify the definition a bit further via the
automatically generated unboxAssocList
accessor function:
instance Functor_ (AssocList k) where
-- map :: (a -> b) -> AssocList k a -> AssocList k b
-- map f list = BoxAssocList $ Functor_.map (Functor_.map f) (unboxAssocList list)
-- map f list = BoxAssocList $ Functor_.map (Functor_.map f) . unboxAssocList $ list
map f = BoxAssocList . Functor_.map (Functor_.map f) . unboxAssocList
Aside: Thinking back to the "type-level lambda" we wanted to
write, it turns out we can factor out that pattern of
composition in a more general wrapper type than
AssocList
above:
newtype Compose f g x = Compose { getCompose :: f (g x) }
instance (Functor_ f, Functor_ g) => Functor_ (Compose f g) where
-- map :: (a -> b) -> Compose f g x -> Compose f g x
-- map f (Compose x) = Compose (Functor_.map (Functor_.map f) x)
map f = Compose . Functor_.map (Functor_.map f) . getCompose
type AssocList k = Compose [] ((,) k)
From Functor_
to Functor
The Functor
class defined in Data.Functor
(and exposed
by Prelude
) uses the name fmap
rather than map
.
class Functor f where
fmap :: (a -> b) -> f a -> f b -- Functor_.map
As mentioned before, there is an infix operator defined to
be fmap
.
(<$>) = fmap
This allows the expression f <$> x
to resemble
function application f $ x
.