Different behaviors of Applicative on tuples and l

2019-06-28 07:31发布

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.

2条回答
beautiful°
2楼-- · 2019-06-28 08:04

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:

fmap succ [1,2,3]  ==> [2,3,4]
fmap succ ("a", 4) ==> ???

It would be illogical to argue that fmap applies succ to "a". What happens is that only the second component gets affected:

fmap succ ("a", 4) ==> ("a", 5)

Indeed, look at the instances:

instance Functor [] where ...
instance Functor ((,) a) where ...

Notice the a type. In the list instance, [] takes only one type parameter, and that's the type affected by fmap. In (,) we have two type parameters: one is fixed (to a) and does not change when applying fmap -- 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.,

instance Functor (\b -> (b,b)) where ...

but Haskell does not allow this. If needed, one needs a newtype wrapper:

newtype P b = P (b,b)
instance Functor P where
   fmap f (P (x,y)) = P (f x, f y)
查看更多
SAY GOODBYE
3楼-- · 2019-06-28 08:09

An Applicative is whatever combination of a datatype and of definitions of pure and <*> that satisfies the applicative laws:

    [identity] pure id <*> v = v
 [composition] pure (.) <*> u <*> v <*> w = u <*> (v <*> w)
[homomorphism] pure f <*> pure x = pure (f x)
 [interchange] u <*> pure y = pure ($ y) <*> u

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 of Maybe 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 auxiliary ZipList newtype, where <*> zips lists together and pure constructs an infinite list.

查看更多
登录 后发表回答