Haskell的Bifunctor与范畴论双函子差异:我的理解是否有误?
范畴论中对双函子的定义是:
定义域为乘积范畴的函子:对于范畴C₁、C₂和D,函子F:C₁×C₂⟶D被称为从C₁和C₂到D的双函子
Haskell标准库中的Bifunctor类原始定义为:
class Bifunctor p where bimap :: (a -> b) -> (c -> d) -> p a c -> p b d
你提出的、试图贴合范畴论定义的MyBifunctor类:
class MyBifunctor p where myBimap :: ((a,c) -> (b,d)) -> p a c -> p b d
另外,自base 4.18.0版本起,Bifunctor类新增了量化约束:
class (forall a. Functor (p a)) => Bifunctor p where bimap :: (a -> b) -> (c -> d) -> p a c -> p b d
针对你的疑问逐一解答
你的理解存在部分偏差,Haskell的
Bifunctor完全符合范畴论双函子定义
范畴论里乘积范畴C₁×C₂中的态射,是由C₁中的态射a→b和C₂中的态射c→d组成的成对独立态射,而非任意的(a,c)→(b,d)二元函数。Haskell的bimap参数正好对应这种成对态射——它接收两个独立的函数(对应乘积范畴的态射对),再作用于双函子实例,这完全匹配范畴论双函子对态射的映射要求。Haskell的
Bifunctor是标准范畴论双函子的Haskell实现
新增的forall a. Functor (p a)约束,是要求当固定p的第一个参数时,它关于第二个参数是函子;同时,Bifunctor还隐含了固定第二个参数时关于第一个参数也是函子(可通过first f = bimap f id推导得到)。这正好对应双函子的核心性质:双函子F:C₁×C₂→D可拆解为两个方向的函子性,即固定一个参数后,剩余参数构成的函子,这和范畴论对双函子的要求完全一致。
你觉得它像“两个函子的乘积”,本质上是因为双函子本身就具备对两个参数分别的函子性,Haskell的Bifunctor只是用bimap把这两个函子性统一表达了出来。
MyBifunctor类不符合范畴论双函子的定义
如前所述,范畴论乘积范畴的态射是成对的独立态射,不包含那些会让两个参数产生依赖的“交叉”函数(比如输入(a,c)后,根据c的值修改a的函数)。myBimap接收的任意(a,c)->(b,d)函数,覆盖了乘积范畴中不存在的态射类型,因此这个类对应的不是范畴论意义上的双函子,只是一种更宽泛的二元类型映射,不具备双函子的范畴论特性。
内容的提问来源于stack exchange,提问作者141592653

