Applicative 对 Haskell 中的元组和列表的不同行为

Different behaviors of Applicative on tuples and lists in Haskell

例如,

-- 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]

对于三个示例,<*> 显示了不同的行为。发生什么了?为什么在第三种情况下,它期望 [Char] -> [Char] 而不是 [Char] 就像第一种情况一样。更重要的是,即使元组中只有[Char]<*>也将它们组合在一起。

区别在于列表是同质的,而元组不是:列表只包含相同类型的元素,而元组则不必。

即使不看应用程序,仿函数也已经显示出主要区别:

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

争论 fmap 适用于 succ"a" 是不合逻辑的。发生的情况是只有 second 组件受到影响:

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

确实,看实例:

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

注意 a 类型。在列表实例中,[] 只接受一个类型参数,即受 fmap 影响的类型。在 (,) 中,我们有 两个 类型参数:一个是固定的(到 a)并且在应用 fmap 时不会改变——只有第二个确实。

请注意,当两个类型参数被强制相同时,理论上可以接受 Functor (,) 的实例。例如,

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

但是Haskell不允许这样做。如果需要,需要一个新类型的包装器:

newtype P b = P (b,b)
instance Functor P where
   fmap f (P (x,y)) = P (f x, f y)

Applicative 是满足 applicative laws:

的数据类型和 pure<*> 定义的任何组合
    [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

这些法则确保 <*> 的行为非常像函数应用程序,但发生在某种依赖于 Applicative 的 "special context" 中。对于 Maybe 的情况,上下文是可能没有值。对于元组,上下文是 "monoidal annotations that accompany each value".

pure<*> 可以对不同的数据类型做非常不同的事情,只要他们遵守法律。

事实上,相同的数据类型可以以不同的方式成为应用程序。列表有 Applicative 实例,其中 <*> "obtains all combinations",还有用辅助 ZipList 新类型实现的实例,其中 <*> 将列表压缩在一起,pure 构造一个无限列表。