In your homework you were implementing the Supply
monad, and while you did implement the necessary functionality, I provided you with the necessary boilerplate code, which was:
instance Functor (Supply s) where
fmap = mapSupply
instance Applicative (Supply s) where
pure = pureSupply
(<*>) = mapSupply2 id
instance Monad (Supply s) where
return = pureSupply
(>>=) = bindSupply
We have already discussed the Monad
type class; today I will introduce the other two.
Functor
Remember that type classes are used when you find that a common functionality is repeated at different types. The functionality captured by the Functor
type class is that of “applying a pure function under a type constructor”.
Type constructors
I believe this is the first time I mention the word type constructor, so let me digress for a moment. Consider this list of types:
Bool
Integer
Maybe
Stream
Supply
It is obvious what a Bool
or what an Integer
is: it describes some value at runtime, such as True
or 42
. The same cannot be said about Maybe
, or Stream
. There are no values of type Maybe
, that simply does not make sense. The type Maybe
is not a proper type of its own, but rather it is a type constructor, in that if you apply Maybe
to another type – such as Bool
, then you get a proper type Maybe Bool
that has values, such as Just True
and Nothing
, that you can inspect and pass around and so on.
The type constructor Supply
is even worse: Supply
is not a proper type, but Supply Bool
is still not a proper type. You need to apply Supply
to two proper types to get something that has values. So Supply
is a type constructor, but of a different kind than Maybe
.
Kinds
Does Maybe Stream
make sense? It does not: Maybe
takes a proper type (like Bool
) and turns it into a new proper type Maybe Bool
. But since Stream
is not a proper type itself, Maybe Stream
does not make sense.
So we have basic types and type constructors, and not all combinations make sense. This is just like with functions and values! And indeed, types and type constructors have their own, separate, type system that ensures that only sensible combinations are used. To keep things separate, the type of a type is called its kind, so we have a kind system.
The basic kind is called star and written *
(in future versions of Haskell, this will be called Type
). Bool
and Integer
have kind *
.
Type constructors such as Maybe
and Stream
have kind * -> *
: They are functions (on the type level) that map types (of kind star) to types (of kind star).
And our Supply
type constructor has kind * -> * -> *
. From this kind signature you can read that you have to apply it to two types of kind star to get a type of kind star.
Another type constructor of that kind is the arrow itself:
Prelude> :kind (->)
(->) :: * -> * -> *
Some more complicated kind signatures, such as (* -> *) -> *
exist, although we did not yet see an example for that. What can you do with such a type constructor?
You can ask GHCi for the kind of a type or type constructor:
Prelude> :kind Bool
Bool :: *
Prelude> :kind Maybe
Maybe :: * -> *
You can also ask for the kind of a type class:
Prelude> :kind Eq
Eq :: * -> GHC.Prim.Constraint
Prelude> :kind Monad
Monad :: (* -> *) -> GHC.Prim.Constraint
This tells us that the Eq
type class has one parameter, which has to be a type of kind star, while Monad
is a type class whose instances are type constructors of kind * -> *
. This makes sense, because in the type signatures of the methods of the Monad type class the parameter of the type class m
is applied to a type. We can therefore not have an instance Monad Bool
, we can have Monad Maybe
, and we cannot have Monad Supply
. But did we not have that in the homework? No! We defined a Monad
instance for Supply s
(for any type s). If you partially apply the type constructor Supply
to one argument, you get something of kind * -> *
.
All you learned about types on the term level applies here as well.
Back to Functor
If I now say that the functionality captured by the Functor
type class is that of “applying a pure function under a type constructor”, that hopefully means more to you.
We have seen the pattern before twice:
mapList :: (a -> b) -> List a -> List b
mapSupply :: (a -> b) -> Supply s a -> Supply s b
and I’m sure you would not doubt the existence and usefulness of a functions
mapMaybe :: (a -> b) -> Maybe a -> Maybe b
mapTree :: (a -> b) -> Tree a -> Tree b -- the tree from last homework
The common pattern that emerges here is
class Functor f where
fmap :: (a -> b) -> f a -> f b
Like any good type class, this comes with a few laws:
fmap id == id
fmap (f . g) == fmap f . fmap g
Remember that f . g
is the function composition of f
and g
.
One implication of these laws is that if f
is something with an effect of some sort (like IO
), then applying a function via fmap
does not not change the effect.
Examples
Functor instances are usually straight-forward to implement. Here are examples:
data Tree a
= Node [Tree a] -- a different tree!
| Leaf a
instance Functor Tree where
fmap f (Node ts) = Node (map (fmap f) ts)
fmap f (Leaf x) = Leaf (f x)
data GenTree f a -- kind (* -> *) -> * -> *
= Node (f (GenTree f a))
| Leaf a
instance Functor f => Functor (GenTree f) where
fmap f (Node ts) = Node (fmap (fmap f) ts)
fmap f (Leaf x) = Leaf (f x)
data Proxy a = Proxy
instance Functor Proxy where
fmap _ Proxy = Proxy
Not all type constructors can be made instances of Functor
. Here are two examples:
data Powerset a = Powerset (a -> Bool)
data Enum a = Enum (a -> Integer) (Integer -> a)
But the vast majority of data types can be nice functions, and it is really useful.
By the way: There is the operator (<$>)
that can be used instead of fmap
in case infix is nicer. This makes the correspondence between f x
(apply f
to the pure value x
) and f <$> x
(apply f
to the result of the computation x
) clearer.
Applicative
The other type class that we instantiated in the homework is Applicative
, which is a generalization of Functor
. There are two ways of motivating the primitive Applicative
combinator:
Deriving <*>
Compare the types of pure function application, conveniently expressed using
$
, andfmap
:($) :: (a -> b) -> a -> b (<$>) :: Functor f => (a -> b) -> f a -> f b
We see that the argument is now hidden behind a type constructor. We reach the primitive combinator of
Applicative
if we do the same to the function:(<*>) :: Applicative f => f (a -> b) -> f a -> f b
This is a real generalization, as you cannot implement that function with just a
Functor
instance.If you think of
f
describing some kind of effect (IO, failure, nondeterminism etc.), then this means that not only the argument in the (generalized) function application is obtained in an effectful way, but also the function.Looking again at the type of
fmap
, we see that it applies an unary function to a now effectfully captured argument:fmap :: Functor f => (a -> r) -> f a -> f r
We can try to generalize that to binary functions:
liftA2 :: Applicative f => (a -> b -> r) -> f a -> f b -> f r
(The function
liftA2
is not part of thePrelude
, but you get it if youimport Control.Applicative
.)Again we find that just using what is provided by
Functor
, we cannot implement this combinator, so this is a real generalization.
Both views are equivalent. If we have <*>
and <$>
from Functor
, we can get liftA2
:
liftA2 f x y = f <$> x <*> y
We can also do the other direction:
f <*> x = liftA2 id f x
What if we generalize liftA2
again to ternary function? Do we get yet another concept? No, we do not! The applicative interface is enough to implement
liftA3 :: Applicative f => (a -> b -> c -> r) -> f a -> f b -> f c -> f r
liftA3 f x y z = f <$> x <*> y <*> z
and in fact, liftA2
etc. are rarely used and chains of <$>
and <*>
are used instead in idomatic code using some applicative-based interface.
Return of the purity
In order to instantiate the Applicative
type class, besides giving a definition for <*>
, we have to give a definition for
pure :: Applicative f => a -> f a
which injects a pure value into whatever f
represents. This is exactly the same as the return
that we discussed two weeks ago, so I will not dwell on it.
Laws
The Applicative type class has laws:
pure id <*> v = v -- identity
pure (.) <*> u <*> v <*> w = u <*> (v <*> w) -- composition
pure f <*> pure x = pure (f x) -- homomorphism
u <*> pure y = pure ($ y) <*> u -- interchange
The main intuition here is that that <*>
is associative (due to the composition law) and that pure
really is pure, and thus can be pushed around.
Example instances
Some types have only one sensible instance for Applicative
. For example
instance Applicative Maybe where
pure = Just
Just f <*> Just x = Just (f x)
_ <*> _ = Nothing
For lists there are two possible instances that come to mind. The first one corresponds to the notion of nondeterministic evaluation that we already know form its Monad
instance, where we apply every function on the left to every argument on the right:
instance Applicative [] where
pure x = [x]
[] <*> xs = []
(f:fs) <*> xs = map f xs ++ fs <*> x
The other one involved zipping lists. Note the peculiar choice of pure
: it has to be that one for the identity law to hold:
instance Applicative [] where
pure x = repeat x
(<*>) = zipWith ($)
The Haskell standard library provides the former instance. If you want the latter, you have to use the ZipList
wrapper provided in Control.Applicative
.
Monad ⊂ Applicative ⊂ Functor
Applicative lies inbetween Functor
and Monad
in the sense that when you have a Monad
instance, you also have an Applicative
instance, and when you have an Applicative
instance, you have a Functor
instance.
To see this, we have to define two Applicative
methods using the Monad
combinators, either directly, or using do
-notation (which might be more educating). pure
is boring, as that is just return
. Here is <*>
:
f <*> x = f >>= (\f' -> x >>= (\x' -> return (f' x')))
-- or
f <*> x = do
f' <- f
x' <- x
return (f' x')
And now we can define fmap
in terms of the Applicative
combinators:
fmap f x = pure f <*> x
Using these definitions we could prove the Applicative
laws from the Monad
laws, and similarly the Functor
laws from the Applicative
laws, but we do not do that now.
It is also expected, i.e. part of the laws, that these equalities hold for any type constructor that has instances of Functor
, Applicative
and Monad
, although they are free to have possibly more efficient implementations.
So every Monad
is an Applicative
. Are there Applicative
instances that do not have a corresponding Monad
instance? Yes there are.
Here is one example:
data ConstString a = ConstString String -- special case of data Const a b = Const a instance Applicative ConstString where pure x = ConstString "" ConstString s1 <*> ConstString s2 = ConstString (s1 ++ s2)
The monad laws require that
return x >>= f = f x
holds, in particular for
x = "Hello"
andf = ConstString
. But sincereturn = pure
andpure
throws away its argument, it is easy to see that no definition of(>>=)
can do that.Another example is
ZipList
. Unfortunately, there is no simple argument why that is the case, but if you want you can find many posts online of people saying they foundMonad
instances, only to be told in what way theirMonad
instance does not work.
Why all this?
So what is the point of having Applicative
if Monad
is stronger, and you can do more with it? Because these are interfaces, e.g. between a library implementation and the library-using code, one side’s power is the other side’s constraint.
If you look at the type signature of monadic bind it is clear that sequencing is forced there: Until the first computation has not yielded a result, the second cannot start. This is different for <*>
or liftM2
: If the effect would be “read something from the network”, then it is now clear that the implementation could choose to parallelize these requests and start with the second even before the first one ended. The power to do this optimization relies on the restraint that you put on the user of the interface. Facebook is using that with good results in their HaXL
library.
We will look in depth at an example for that power next week, and the homework sets the stage for that.