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, because Map
doesn't actually have a monad (nor even Applicative
) instance. A direct adaption of the list/array implementation would indeed not be possible... recap: on lists, we have
pure x ≡ [x]
(,) <$> [a,b] <*> [x,y,z] ≡ [(a,x),(a,y),(a,z),(b,x),(b,y),(b,z)]
so 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:
pure x ≡ repeat x
(,) <$> [a,b] <*> [x,y,z] ≡ [(a,x),(b,y)]
Notice that the indices of the elements are preserved.
Now this instance could in fact be adapted for Map
, if only there was a repeat :: 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 a Map
would entail. But if we restrict ourselves to key types with only finitely many possible values (such as Bool
), then we're good:
instance Applicative (Map Bool) where
pure x = Map.fromList [(False, x), (True, x)]
<*> = Map.intersectWith ($)
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.