For example,
-- Num a => ([Char], a -> a) <*> ([Char], a)
> ("hello ",(*6)) <*> ("world",7)
("hello world",42)
-- Num a => [a -> a] <*> [a]
> [(*7),(*6)] <*> [6,7]
[42,49,36,42]
-- Num a => [[Char], a -> a] <*> [[Char], a]
> ["hello ",(*6)] <*> ["world",7]
<interactive>:17:2:
Couldn't match expected type ‘[Char] -> [Char]’
with actual type ‘[Char]’
In the expression: "hello "
In the first argument of ‘(<*>)’, namely ‘["hello ", (* 6)]’
In the expression: ["hello ", (* 6)] <*> ["world", 7]
For three examples, <*>
shows different behaviors. What happens? Why in the third case, it expects a [Char] -> [Char]
rather than [Char]
just like in the first case. What's more, even there's only [Char]
in tuples, <*>
combines them together.
The difference lies in the fact that lists are homogeneous, while tuple are not: lists contain only elements of the same type, while tuples do not have to.
Even without looking at applicatives, functors already show a main difference:
It would be illogical to argue that
fmap
appliessucc
to"a"
. What happens is that only the second component gets affected:Indeed, look at the instances:
Notice the
a
type. In the list instance,[]
takes only one type parameter, and that's the type affected byfmap
. In(,)
we have two type parameters: one is fixed (toa
) and does not change when applyingfmap
-- only the second one does.Note that it would be theoretically possible to admit an instance of
Functor (,)
when both type arguments are forced to be the same. E.g.,but Haskell does not allow this. If needed, one needs a newtype wrapper:
An Applicative is whatever combination of a datatype and of definitions of
pure
and<*>
that satisfies the applicative laws:These laws ensure that
<*>
behaves very much like function application, but one taking place in some kind of "special context" that depends on the Applicative. For the case ofMaybe
the context is the possible absence of value. For tuples, the context is "monoidal annotations that accompany each value".pure
and<*>
can do very different things for different datatypes as long as they respect the laws.In fact, the same datatype can be an Applicative in different ways. Lists have the Applicative instance in which
<*>
"obtains all combinations", but also the instance implemented with the auxiliaryZipList
newtype, where<*>
zips lists together andpure
constructs an infinite list.