从程序员的范畴论中理解双函子 - Ch。 8个

Understanding of bifunctor from Category Theory for Programmers - Ch. 8

我要和 Chapter 8 from Category Theory for Programmers 在一起了。

在 8.3 节中,Bartosz 定义了这种类型

newtype BiComp bf fu gu a b = BiComp (bf (fu a) (gu b))

在这里,如果我稍微了解一下 Haskell,bffugu 是类型构造函数,bf (* -> *) -> (* -> *) -> * -> * -> *fugu 属于 * -> *(就像 Maybe[]),而 ab 是 kind * 的一般类型; BiComp =的左边是一个类型构造函数,写起来很长,而BiComp 右边是一个值构造函数,所以是(bf (fu a) (gu b)) -> BiComp bf fu gu a b.

类型

然后作者在ab中使BiComp成为双函子,前提是类型构造函数参数bf也是Bifunctor,并且类型构造函数 fuguFunctors:

instance (Bifunctor bf, Functor fu, Functor gu) => Bifunctor (BiComp bf fu gu) where
    bimap f1 f2 (BiComp x) = BiComp ((bimap (fmap f1) (fmap f2)) x)

到目前为止一切顺利,在这一点上对我来说一切似乎都是合理的。除了对类型构造函数和值构造函数使用相同的名称可能会让我迷路。

现在我很想做出以下观察:

这是正确的吗?

如果是,那我没看懂下面的内容

bimap :: (fu a -> fu a') -> (gu b -> gu b') -> bf (fu a) (gu b) -> bf (fu a') (gu b')

因为这个是右边bimap的类型,就是我在上面bullet point写的那个,只不过是用a = fu a, a' = fu a', 依此类推。

我是不是漏掉了什么(或者想多了...)?

你很接近。

首先,你的那种bf错了。它实际上只是 * -> * -> *,这完全符合预期,因为它将成为 Bifunctor。当然,那种BiComp是相当疯狂的:

BiComp :: (* -> * -> *) -> (* -> *) -> (* -> *) -> * -> * -> *

至于要点中的类型,从技术上讲,它们都是正确的,但使用新的类型变量(尤其是第一个要点中的类型!)可能有助于使这一切更清楚.实际上,右侧的 bimap 具有类型

bimap :: forall c c' d d'. (c -> c') -> (d -> d') -> bf c d -> bf c' d'

我们需要使用它来制作将类型 bf (fu a) (gu b) 的输入值转换为类型 bf (fu a') (gu b') 的输出值的东西。我们只有让 c ~ fu a, c' ~ fu a', d ~ gu b, d' ~ gu b' 才能做到这一点。让我们看看它对我们的 RHS 做了什么 bimap:

bimap :: (fu a -> fu a') -> (gu b -> gu b') -> bf (fu a) (gu b) -> bf (fu a') (gu b')

啊哈!这正是您在右侧找到的!而且,我们可以准确地提供我们需要的论据。首先,一个 fu a -> fu a' 类型的函数。好吧,我们有一个给定的函数 f1 :: a -> a' 并且我们知道 fu 是一个函子,所以我们可以用 fmap f1 得到我们需要的函数。同样 f2fmap f2,一切都很好。