Chris Taylor 的回答恰到好处,但另一种看待它的方式,我觉得更直观,是这样的:Applicative 函数类型实例的作用是这样的:
- 将相同的参数值“提供”给具有相同参数类型的两个函数;
- 将它们的结果与另一个函数结合起来。
所以基本上,如果您有 f :: t -> a 和 g:: t -> b,Applicative 实例允许您将函数 h :: a -> b -> c 映射到 a 和 b 结果,假设 f 和 @987654329 @ 将得到相同的参数。
所以想想你会如何以一种不复杂的方式编写回文测试:
palindrome :: Eq a => [a] -> Bool
palindrome xs = xs == reverse xs
xs 在定义的右侧出现两次:一次作为== 的参数,第二次作为reverse 的参数。这会自动告诉您可能有一种方法可以使用 (->) t 应用程序实例来消除重复。一个不同的,也许更直观的攻击方法是首先将函数重写为:
palindrome xs = id xs == reverse xs
...其中id x = x 是身份函数,它只返回其参数(并且是标准库函数)。现在您可以使用标准 Applicative 成语 (f <$> a0 <*> ... <*> an) 将其重写为:
-- Function that feed the same argument value to both `id` and `reverse`,
-- then tests their results with `==`:
palindrome = (==) <$> id <*> reverse
现在我们可以询问是否有办法在重写中摆脱id。因为<$>只是fmap的简写,所以我们可以研究(->) t的Functor实例,这只是表达函数组合的另一种方式:
instance Functor ((->) t) where
-- Mapping `f` over a *function* `g` is just the same as composing `f`
-- on the results of `g`.
fmap f g = f . g
函数组合最重要的属性之一是对于任何函数f:
f . id = f
因此,将其应用于上述palindrome 的版本,我们得到:
-- Since `f . id = f` for all `f`, then `(==) <$> id` is just `(==)`:
palindrome = (==) <*> reverse