我已尝试使用这些类型进行此操作,但我仍然难以理解其工作原理。
假设:
> :t (==)
(==) :: Eq a => a -> a -> Bool
> :t (<*>)
(<*>) :: Applicative f => f (a -> b) -> f a -> f b
> :t reverse
reverse :: [a] -> [a]
> :t (==) <*> reverse
(==) <*> reverse :: Eq a => [a] -> Bool
直观地我可以理解它将相等运算符与反向结合起来,它创建了一个函数,用于检查反转列表是否与原始列表相等,但实际上并不是更多的信息。已经很明显了。
有人可以更详细地分析这里实际发生的事情的内部结构吗?
答案 0 :(得分:7)
以(<*>)
> : t (<*>)
(<*>) :: Applicative f => f (a -> b) -> f a -> f b
现在(->) k
是Applicative
的实例,带有实现
instance Applicative ((->) k) where
pure a = \_ -> a
f <*> g = \k -> f k (g k)
特别是(<*>)
专门用于(->) k
的类型是
(<*>) :: (k -> a -> b) -> (k -> a) -> (k -> b)
因此应用程序(==) <*> reverse
是
(==) <*> reverse = \k -> (==) k (reverse k)
= \k -> k == reverse k
即。它会检查列表是否与其相反。
答案 1 :(得分:2)
克里斯·泰勒的答案是正确的,但另一种看待它的方式,我觉得更直观,是:函数类型Applicative
实例的作用是:
基本上,如果您有f :: t -> a
和g:: t -> b
,Applicative
实例可让您将功能h :: a -> b -> c
映射到a
和b
结果,假设f
和g
将被提供相同的参数。
所以想想你如何以非复杂的方式写下回文测试:
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
的简写,我们可以研究Functor
的{{1}}实例,这只是表达函数组合的另一种方式:
(->) t
功能组合最重要的特性之一是任何函数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