我一直在阅读 Applicative Functors ,我很难协调category theory和functional programming各自术语的不匹配。
虽然我查看了各种博客,但我用于本研究的最完整资源是:
在类别理论中,functor是从source类别到target类别(在类别类别中)的态射。 “类别类别”包含一组对象,这些对象包含源类别和目标类别以及包含以下内容的仿函数集合:源类别的标识函数;目标类别的身份仿函数;并且,将源连接到目标的仿函数(如果源类别与目标类别相同,并且所讨论的仿函数是标识,那么只需要那个仿函数)。
在函数式编程中,applicative functor被描述为一对操作:
pure : a -> f a
<*> : f ( a -> b) -> f a -> f b
。 什么解释清楚说明了应用函子的函数式编程定义与函子的类别理论定义之间的对应关系?
更具体地说,元组(pure,<*>)
的哪些部分对应于:
注意:我认识到这可能是一个不完整的比喻,对于我提到的每个概念,可能没有一对一的对应关系。我有意避免分享我对这里明显的对应关系的猜测,以便使我的问题变得简单,并避免进一步混淆问题。
答案 0 :(得分:19)
释义this answer:应用仿函数是仿函数,其中还存在保留源/目标类别的单一结构的自然变换。在Haskell的Applicative
endofunctors(因为它们的源和目标类别是Hask)的情况下,幺半群结构是笛卡尔积。因此,对于Applicative
仿函数,存在自然变换φ: (f a, f b) -> f (a, b)
和ι: () -> f ()
。所以我们可以将Applicative
定义为
class Functor f => Applicative' f where
φ :: (f a, f b) -> f (a, b)
ι :: f () -- it could be \() -> f (),
-- but \() -> ... is redundant
此定义等同于标准定义。我们可以表达
φ = uncurry (liftA2 (,))
= \(x, y) -> (,) <$> x <*> y
ι = pure ()
反过来
pure x = fmap (\() -> x) ι
f <*> x = fmap (uncurry ($)) (φ (f, x))
因此,pure
和<*>
是另一种定义此自然转换的方法。
答案 1 :(得分:10)
首先查看类Functor
(这是Applicative
的超类)可能更简单。 Applicative
对应于“松散的幺正编码器”,正如您链接的第一篇论文所指出的那样。 Functor
的定义是:
class Functor f where
fmap :: (a -> b) -> f a -> f b
Functor
的实例是类型构造函数(种类* -> *
)。一个例子是Maybe
。
我们所讨论的类别是“Hask”类别,它将Haskell类型作为对象,并且(单态)Haskell函数作为态射。 Functor
(以及Applicative
,Monad
等)的每个实例都是此类别中的endofunctor,即从类别到其自身的仿函数。
仿函数的两个映射--Haskell-types-to-Haskell-types和Haskell-functions-Haskell-functions--是类型构造函数f
和函数fmap
。
例如,Int
和Maybe Int
都是Hask中的对象; Maybe
从前者映射到后者。如果chr :: Int -> Char
,则fmap
会将其映射到fmap chr :: Maybe Int -> Maybe Char
。 Functor
法律对应于分类函子法则。
在Applicative
的情况下,Functor
是一个超类,所以我刚刚说过的所有内容都适用。在这种特殊情况下,您可以使用fmap
和pure
- <*>
来实现liftA f x = pure f <*> x
- 因此您正在寻找的仿函数的两个部分是{{1} }和f
。 (但请注意liftA
的其他表述不允许您这样做 - 通常您会依赖Applicative
超类。)
我不太确定你所说的“算子所在的类别”。
答案 2 :(得分:1)
你提出问题的方式表明你正在寻找一类类别,其态射可以被理解为Haskell仿函数。那将是种类。该类别有一个对象,它是*,表示haskel类型的类别。由于只有一个对象,因此只需要有一组态射,这是类型构造函数(种类* - > *)。并非每个类型构造函数都是一个仿函数,但每个仿函数都是一个类型构造函数。所以在这个意义上它可以被理解为从*到*的态射。
种类的观点当然是跟踪类型构造函数的参数数量。要理解这个类别在某种程度上是人为的,因为它只有一个对象。
此外,对象*
没有真正的身份态射。你可以想到Identity :: * -> *
,但它不是一个严格意义上的身份(它取决于自然的同构,因为你有Identity :: forall a . a -> Identity a
和runIdentity :: forall a . Identity a -> a
)。组合也是如此:你总是必须使用显式的同构来处理组合的仿函数(Compose
/ getCompose
)。