25

我一直在阅读Applicative Functors并且我很难调和类别理论函数式编程各自术语中的不匹配。

虽然我浏览了各种博客,但我用于这项研究的最完整的资源是:

在范畴论中,函子是从范畴到目标范畴(在范畴的范畴中)的态射。“category of categories”有一个包含源类别和目标类别的对象集合,以及一个包含以下内容的函子集合:源类别的恒等函子;目标类别的恒等函子;并且,连接源和目标的函子(如果源类别与目标类别相同,并且所讨论的函子是恒等式,则只需要一个函子)。

在函数式编程中,应用函子被描述为一对操作:

  • pure : a -> f a
  • <*> : f ( a -> b) -> f a -> f b.

这是我的问题

什么解释清楚了应用函子的函数式编程定义和函子的范畴理论定义之间的对应关系?

更具体地说,元组的哪些部分(pure,<*>)对应于:

  1. 源类别
  2. 目标类别
  3. 函子所在的范畴
  4. 函子对源类别对象的结构保持效果
  5. 函子对源范畴态射的结构保持效应

笔记:我承认这可能是一个不完整的比喻,我提到的每个概念可能都没有一一对应的关系。我故意避免在这里分享我对明显对应关系的猜测,以保持我的问题简单并避免进一步混淆问题。

4

3 回答 3

19

解释这个答案:应用函子是函子,它还有一个自然变换,可以保留其源/目标类别的单面结构。在 Haskell 的Applicativeendofunctors 的情况下(因为它们的源和目标类别是 Hask),monoidal 结构是笛卡尔积。所以对于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<*>是如何定义这种自然转换的另一种方法。

于 2013-06-29T09:55:25.413 回答
10

首先查看类Functor(它是 的超类Applicative)可能更简单。Applicative正如您链接到的第一篇论文指出的那样,对应于“宽松的幺半群函子”。的定义Functor是:

class Functor f where
  fmap :: (a -> b) -> f a -> f b

的实例Functor是类型构造函数( kind * -> *)。一个例子是Maybe

我们正在谈论的类别是“Hask”类别,它具有作为对象的 Haskell 类型和作为态射的(单态)Haskell 函数。Functor(and Applicative, Monad, etc.) 的每个实例都是该类别中的一个内函子,即从该类别到其自身的函子。

函子的两个映射——Haskell-types-to-Haskell-types 和 Haskell-functions-Haskell-functions——是类型构造函数f和函数fmap

例如,IntMaybe Int都是 Hask 中的对象;Maybe从前者映射到后者。如果chr :: Int -> Char,则将其fmap映射到fmap chr :: Maybe Int -> Maybe Char。这些Functor定律对应于范畴函子定律。

对于Applicative,Functor是一个超类,所以我刚才所说的一切都适用。在这种特殊情况下,您可以fmap使用pureand <*>- liftA f x = pure f <*> x- 所以您正在寻找的仿函数的两个部分是fand liftA。(但请注意,其他公式Applicative不允许您这样做 - 通常您会依赖Functor超类。)

我不太清楚你在这里所说的“函子所在的类别”是什么意思。

于 2013-06-29T03:00:55.683 回答
1

您提出问题的方式表明您正在寻找一类类别,其态射可以理解为 Haskell 函子。那将是种类。该类别有一个对象是*,表示haskel 类型的类别。因为只有一个对象,所以只有一组态射,即类型构造函数(类型 * -> *)。不是每个类型构造函数都是函子,但每个函子都是类型构造函数。所以在这个意义上可以理解为从*到*的态射。

种类的重点当然是跟踪类型构造函数的参数数量。将其理解为一个类别在某种程度上是人为的,因为它只有一个对象。

此外,对象没有真正的恒等态射*。你可以想到Identity :: * -> *,但它不是严格意义上的身份(虽然它取决于自然同构,因为你有Identity :: forall a . a -> Identity aand runIdentity :: forall a . Identity a -> a)。组合也是如此:您总是必须使用显式同构来处理组合仿函数(Compose/ getCompose)。

于 2013-08-16T11:26:05.267 回答