I've just realized, that Functions have instances for Monad, Functor and Applicative.
What I usually do, when I see some typeclass instance I don't get, is write some well-typed expression and see what it returns:
Can somebody explain these instances? You usually hear about the instances for List and Maybe, which by now are natural to me, but I don't understand how Functions can be a Functor or even a Monad.
EDIT: Okay, this is a valid well-typed expression that doesn't compile:
fmap (+) (+) 1 (+1) 1
fmap
for functions acts on the results produced by the function:The
a
result of thet -> a
function is modified through ana -> b
function. If that sounds a lot like function composition, that is because it is exactly that:(<*>)
is a bit trickier:The
Applicative f => f (a -> b)
argument becomest -> (a -> b)
.(<*>)
converts a function of two arguments (of typet -> a -> b
) into a function of one argument (of typet -> b
) by using an auxiliary function (of typet -> a
) to generate the second argument from the first:Here is an example written in applicative style, using the
Functor
andApplicative
instances of functions:One way of reading it is "feed
2
to(> 0)
and to(< 4)
, and combine the results with(&&)
". It can be written in a more compact way withliftA2
fromControl.Applicative
, but I believe the(<$>)
/(<*>)
spelling is more intention-revealing.The other method of
Applicative
,pure
...... makes a
t -> a
function out of ana
and nothing else. A constant function is the only way of doing that:Do note that
pure 2
has a different type in each of the examples above.Given all of the above, the
Monad
instance is surprisingly uninteresting. For extra clarity, let's look at(=<<)
rather than at(>>=)
:If you compare this type with the one of
(<*>)
, you will see they are the same, except that the first argument has been flipped. The function instances are an exceptional case in whichApplicative
andMonad
do essentially the same thing.It is worth mentioning that
join
fromControl.Monad
can be used to use a value as both arguments of a two-argument function:First, I agree with you: functions are not very intuitive as functors, and indeed I sometimes wish these instances weren't there. It's not that they aren't useful sometimes, but quite as often they're used in a needless and confusing manner. These methods can always be replaced with either more specific combinators (in particular from
Control.Arrow
), or with the equivalent but somehow much more descriptive reader monad.That said... to understand the function functor, I suggest you first consider
Map
. In a way,Map Int
is a lot like an array: it contains some elements that you can transform (i.e.fmap
over), and you can access individual elements by indexing them with integers.Map
just allows the “array” to have gaps in it, and generalises from integer-indices to any sort of index that can be ordered.On another view though,
Map
is just a specific implementation of a function: it associates arguments (keys) to results (values). And that should make it quite clear how the function functor works: it fmaps over all possible results† of the function.This explanation unfortunately doesn't much explain the
Monad
instance, becauseMap
doesn't actually have a monad (nor evenApplicative
) instance. A direct adaption of the list/array implementation would indeed not be possible... recap: on lists, we haveso after combining, the indices are all different. That can't work for
Map
where we want to support generic keys.However, there's an alternative monad instance for list, the zip list:
Notice that the indices of the elements are preserved.
Now this instance could in fact be adapted for
Map
, if only there was arepeat :: a -> Map k a
generator. This doesn't exist because in general there are infinitely many keys and we can't enumerate them all nor balance the infinite tree that such aMap
would entail. But if we restrict ourselves to key types with only finitely many possible values (such asBool
), then we're good:Now, that's exactly how the function monad works, just unlike with
Map
there's no problem if infinitely many different arguments are possible, because you never attempt to store all of them with associated values; rather you always only compute the values on the spot.†That would be infeasible if it weren't done lazily – which in Haskell is hardly a problem, in fact if you fmap over a
Map
it also happens lazily. For the function functor,fmap
is actually not just lazy but the result is also immediately forgotten and needs to be recalculated.