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
构造一个无限列表。
例如,
-- 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
构造一个无限列表。