编辑:例如,为什么选择“applicative”这个单词来命名该类,而不是其他单词?这个类有什么特点使得“Applicative”成为它的好名称(即使付出了其含义不明确的代价)?
谢谢!
在不知道上下文的情况下,“applicative”指的是什么并不清楚。
如果它确实没有指的是适用函子(即Applicative
),那么它可能指的是应用本身的形式:f a b c
是一种“applicative”形式,这也是适用函子得名的原因:f <$> a <*> b <*> c
是类似的。(事实上,习语括号进一步将其联系在一起,让您可以将其写成(| f a b c |)
。)
同样,与不基于函数对参数应用(通常以前缀形式出现)的语言相比,“applicative语言”可以进行对比;例如,串联(“基于堆栈”的)语言并非applicative。
要深入回答为什么适用函子被称为它们所说的,请阅读 带效果的适用程序设计。基本思想是很多情况都需要类似于“增强应用”的东西:在某些具有效果的上下文中应用纯函数。比较一下map
和mapM
的定义:
map :: (a -> b) -> [a] -> [b]
map _ [] = []
map f (x:xs) = f x : map f xs
mapM :: (Monad m) => (a -> m b) -> [a] -> m [b]
mapM _ [] = return []
mapM f (x:xs) = do
x' <- f x
xs' <- mapM f xs
return (x' : xs')
使用mapA
(通常称为traverse
):
mapA :: (Applicative f) => (a -> f b) -> [a] -> f [b]
mapA _ [] = pure []
mapA f (x:xs) = (:) <$> f x <*> mapA f xs
正如您所看到的,mapA
更加简洁,而且更明显地与 map
相关(如果在 map
中也使用前缀形式的 (:)
,那么就更明显了)。实际上,即使在具有完整Monad
的情况下,使用applicative functor符号也很常见,因为它通常更清晰易懂。
查看定义也是有帮助的:
class (Functor f) => Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
将 (<*>)
的类型与应用的类型进行比较:($) :: (a -> b) -> a -> b
。 Applicative
提供了一种通用的“提升”应用程序的形式,使用它的代码以一种applicative风格编写。Applicative
是SK combinator calculus的一般化;pure
是 K :: a -> (r -> a)
(又名 const
)的一般化,而 (<*>)
是 S :: (r -> a -> b) -> (r -> a) -> (r -> b)
的一般化。 r -> a
部分被简单地泛化为 f a
;原始类型是通过 ((->) r)
的 Applicative
实例获得的。pure
还允许您以更统一的方式编写applicative表达式:pure f <*> effectful <*> pure x <*> effectful
,而不是 (\a b -> f a x b) <$> effectful <*> effectful
。更基本地说,"applicative" 可以理解为在某种形式的SK演算中运作。这也是 Applicative
类的意义所在。它提供了组合子 pure
(K的泛化)和 <*>
(S的泛化)。
当你的代码以这种风格表达时,就可以称之为 applicative。例如以下代码:
liftA2 (+) sin cos
是一个应用表达式
\x -> sin x + cos x
当然,在Haskell中,Applicative
类是应用程序编程的主要构造,但即使在单子或箭头上下文中,你也可以应用式地编写代码:
return (+) `ap` sin `ap` cos
arr (uncurry (+)) . (sin &&& cos)
最后的代码是否是适用的存在争议,因为有人认为使用适用风格需要柯里化才有意义。
Applicative
也是一个Functor
... 只是因为缺乏远见,我们没有class Functor a => Applicative a
。 - Tom CrockettApplicative
实例都是函子... - Tom Crockett