抱歉,我不是很了解我的数学知识,所以我很好奇如何在Applicative类型类中发音这些函数
我认为,是否了解您的数学与这里无关。您可能已经知道,Haskell从抽象数学的各个领域中借用了一些术语,最著名的是范畴论,从中我们得到了函子和单子。在Haskell中,这些术语的使用与形式上的数学定义有所不同,但是无论如何它们通常足够接近以至于是好的描述性术语。
该Applicative
类型的类位于介于Functor
和Monad
,所以人们期望它有一个类似的数学基础。该Control.Applicative
模块的文档开始于:
该模块描述了函子和monad之间的中间结构:它提供纯表达和排序,但不提供绑定。(从技术上讲,是一个强大的松散单曲面函子。)
嗯
class (Functor f) => StrongLaxMonoidalFunctor f where
. . .
Monad
我想不像它那么吸引人。
所有这些基本上可以归结为Applicative
与任何在数学上特别有趣的概念都不对应,因此周围没有现成的术语可以捕捉其在Haskell中的使用方式。因此,暂时搁置数学。
如果我们想知道该叫(<*>)
什么,可能会有助于了解它的基本含义。
那么到底是怎么回事Applicative
,为什么我们要这样称呼呢?
什么Applicative
在实践中达是解除的方式任意功能集成到一个Functor
。考虑Maybe
(可以说是最简单的非平凡的Functor
)和Bool
(同样是最简单的非平凡的数据类型)的组合。
maybeNot :: Maybe Bool -> Maybe Bool
maybeNot = fmap not
该功能fmap
使我们可以not
从进行中Bool
升级Maybe Bool
。但是,如果我们想举起(&&)
呢?
maybeAnd' :: Maybe Bool -> Maybe (Bool -> Bool)
maybeAnd' = fmap (&&)
好吧,那根本不是我们想要的!实际上,这几乎没有用。我们可以试着聪明和潜入另一Bool
成Maybe
通过背...
maybeAnd'' :: Maybe Bool -> Bool -> Maybe Bool
maybeAnd'' x y = fmap ($ y) (fmap (&&) x)
...但是那不好。一方面,这是错误的。另一方面,它很丑。我们可以继续尝试,但是事实证明,没有办法提升多个参数的功能来处理任意参数Functor
。烦人!
另一方面,如果使用Maybe
的Monad
实例,我们可以轻松实现:
maybeAnd :: Maybe Bool -> Maybe Bool -> Maybe Bool
maybeAnd x y = do x' <- x
y' <- y
return (x' && y')
现在,仅翻译一个简单的函数Control.Monad
就很麻烦-这就是为什么提供自动执行该功能的原因liftM2
。名称中的2指的是它对正好两个参数的函数起作用;3、4和5参数函数也存在类似的功能。这些函数更好,但不是完美的,并且指定参数的数量很难看又笨拙。
这使我们进入介绍Applicative类型类的论文。在其中,作者基本上提出了两个观察结果:
- 将多参数函数提升为a
Functor
是很自然的事情
- 这样做并不需要具备
Monad
正常函数应用程序是通过简单的并置术语编写的,因此,为了使“提升的应用程序”尽可能简单自然,本文引入了infix运算符来代表应用程序,并提升为Functor
,并提供了一个类型类来提供所需的内容。
所有这些将我们带到以下几点:(<*>)
简单地表示函数应用程序-那么,为什么在发音上与在空白处“并置运算符”不同?
但是,如果这还不是很令人满意,我们可以观察到该Control.Monad
模块还提供了对monad执行相同操作的函数:
ap :: (Monad m) => m (a -> b) -> m a -> m b
ap
当然,“应用”的缩写在哪里。由于any Monad
可以是Applicative
并且ap
仅需要后者中存在的功能的子集,我们也许可以说,如果(<*>)
不是运算符,则应将其称为ap
。
我们还可以从另一个方向着手。在Functor
提升操作被称为fmap
,因为它是一个泛化map
的列表操作。列表上什么样的功能会起作用(<*>)
?ap
当然,列表上有什么功能,但这本身并不是特别有用。
实际上,列表可能有一种更自然的解释。当您查看以下类型签名时,会想到什么?
listApply :: [a -> b] -> [a] -> [b]
将列表并行排列,将第一个函数中的每个函数应用于第二个函数中的相应元素,这一想法很吸引人。不幸的是,对于我们的老朋友Monad
,如果列表长度不同,此简单操作将违反monad法则。但这很好Applicative
,在这种情况下,它(<*>)
成为将广义版本组合在一起的一种方式,所以我们可以想象调用它吗?zipWith
fzipWith
这个拉链的想法实际上带给我们了一个完整的圈子。还记得以前关于算式函子的数学知识吗?顾名思义,这是组合类半体和函子的结构的一种方法,它们都是熟悉的Haskell类型类:
class Functor f where
fmap :: (a -> b) -> f a -> f b
class Monoid a where
mempty :: a
mappend :: a -> a -> a
如果将它们放在一个盒子中并摇晃一下,它们会是什么样?从此Functor
我们将使结构的思想独立于其类型参数,并从其Monoid
将函数的整体形式保留下来:
class (Functor f) => MonoidalFunctor f where
mfEmpty :: f ?
mfAppend :: f ? -> f ? -> f ?
我们不想假设有一种创建真正的“空”的方法Functor
,并且我们不能想出任意类型的值,因此我们将修复mfEmpty
as 的类型f ()
。
我们也不想强迫mfAppend
需要一个一致的类型参数,所以现在我们有了这个:
class (Functor f) => MonoidalFunctor f where
mfEmpty :: f ()
mfAppend :: f a -> f b -> f ?
结果类型是mfAppend
什么?我们有两种任意类型,我们一无所知,因此我们没有太多选择。最明智的做法是同时保留两者:
class (Functor f) => MonoidalFunctor f where
mfEmpty :: f ()
mfAppend :: f a -> f b -> f (a, b)
在这一点mfAppend
现在是明确的一般化版本zip
的列表,我们可以重建Applicative
轻松:
mfPure x = fmap (\() -> x) mfEmpty
mfApply f x = fmap (\(f, x) -> f x) (mfAppend f x)
这也向我们显示了pure
与a的identity元素相关的信息Monoid
,因此它的其他好名称可能是暗示单位值,空操作等的任何名称。
这很长,因此总结一下:
(<*>)
只是一个经过修改的功能应用程序,因此您可以将其读取为“ ap”或“ apply”,也可以像正常功能应用程序一样完全删除它。
(<*>)
也大致概括zipWith
了列表,因此您可以将其读为“ zip函子与”,类似于阅读fmap
为“映射函子与”。
第一个更接近Applicative
类型类的意图-顾名思义,这就是我的建议。
实际上,我鼓励所有解除的应用程序操作员自由使用和不发音:
(<$>)
,将单参数函数提升为 Functor
(<*>)
,该链接通过 Applicative
(=<<)
,它会将输入a的函数绑定Monad
到现有计算中
从本质上讲,这三者只是常规功能的应用,而略微增加了一点。