在Haskell中的Bifunctors与范畴论中的Bifunctors相比较。

4
在Haskell中,类Bifunctor的定义如下:
class Bifunctor p where
  bimap :: (a -> b) -> (c -> d) -> p a c -> p b d

在范畴论中,双函子是根据ncatlab的定义,"简单地指一个定义域为积范畴的函子:对于C1、C2和D三个范畴,从C1和C2到D的函子F:C1×C2⟶D被称为从C1和C2到D的双函子。"
现在如果我要实现这个范畴定义,我会写下以下内容:
class MyBifunctor p where
  myBimap :: ((a,c) -> (b,d)) -> p a c -> p b d

特别是,myBimap看起来很像fmap,这是我认为我们想要的,因为双函子就是一个函子。
现在更进一步,自从base 4.18.0以来,添加了一个量化约束:
class (forall a. Functor (p a)) => Bifunctor p where
    bimap :: (a -> b) -> (c -> d) -> p a c -> p b d

这个量化约束告诉我们,一个双函子是一个“在其第二个参数中”的函子,这显然与范畴定义不符。
我理解,从类“Bifunctor”中,可以得到“一些”双函子,其中第一个和第二个参数的类型不相互作用,但不是“所有”的双函子。实际上,我甚至可以说类“Bifunctor”实现了两个函子的“乘积”,而不是一个真正的双函子。
所以我的问题是:我是否误解了什么?还是Haskell中的双函子实际上并不是双函子?类“MyBifunctor”有意义吗?

1
你基本上是正确的,只是MyBifunctor只是对Functor在对偶对上的函数的限制,这更符合范畴论中的定义,但对我来说似乎并没有立即的实用性。 - undefined
2
我不喜欢(a,c) -> (b,d)这部分——在乘积范畴中,这不是一个态射,因为它应该是(大致上)(a->b, c->d)。问题在于Haskell的表示方式具有误导性:在Haskell中,(a,b)表示的是乘积对象a乘以b,而不是对象对(乘积范畴中的对象)。 - undefined
1
我认为Bifunctor只处理形式为p :: Hask * Hask -> Hask(柯里化)的函子,其中"Hask"是Haskell类型所在的虚构类别。毕竟,Functor类也只处理Hask->Hask的函子。也许可以尝试更通用的方法,使用类似Control.Category.Category的东西,并在那里定义(双)函子。也许在Hackage上的某个包中已经完成了这个(?) - undefined
1
通过“定义域为乘积范畴的函子”,是的,我们确实希望得到乘积范畴的态射。 - undefined
1
@chi https://hackage.haskell.org/package/categories-1.0.7/docs/Control-Categorical-Bifunctor.html - undefined
显示剩余3条评论
2个回答

8
你的MyBifunctor是错误的。在乘积范畴中,态射不是对象对之间的态射(在广义范畴设置中,这意味着什么?),而是对象之间的态射对。比较一下:
((a,c) -> (b,d)) -> p a c -> p b d -- incorrect
(a -> b, c -> d) -> p a c -> p b d -- correct

正确版本在道德上与基础库中的版本相似。
(a -> b) -> (c -> d) -> p a c -> p b d -- base, also correct

这个量化约束告诉我们,一个双函子是其第二个参数的函子,这与范畴定义确实是相符的。
实际上,它确实符合范畴定义。给定一个双函子 B 和第一个源范畴 C 的对象 c,可以定义诱导操作 F = B(c, -),它将对象 d 映射到 B(c, d),将箭头 f : d1 -> d2 映射到 B(id_c, f)。很容易验证 F 满足函子定律:
F(id_d) = B(id_c, id_d) = id_B(c,d) = id_F(d)
F(f) . F(g) = B(id_c, f) . B(id_c, g) = B(id_c . id_c, f . g) = B(id_c, f . g) = F(f . g)

在每种情况下,第二个等式都可以通过双函子法则(或者如果你愿意,可以将乘积范畴作为源范畴的函子法则)来证明。一个几乎相同的论证表明B(-,d)也是一个函子,但在Haskell中很难表达这个约束条件。

2
我想补充一点,一个双函子在第一个参数上也是函子性的——如果你保持第二个参数不变的话。有趣的是,一个在每个参数上都是函子性的映射CxC->C不一定是一个双函子,因为函子性的两个应用在一般情况下可能不可交换。具有这种奇怪的二元运算符的范畴被称为预单子范畴。 - undefined

0
Haskell Bifunctors是“柯里化”的,不同于范畴论,这意味着它们不是来自一个乘积范畴(FunctorOf (Prod (->) (->)) (->)),而是成为一个函子范畴的函子(FunctorOf (->) (Nat (->) (->)))。
这反映了从元组((a, b) -> c)到函数的柯里化,即函数嵌套(a -> (b -> c))。
                   Prod (->) (->)  (->)
                   |               |
                   vvvvvvvvvvvv    vvvv
UncurriedEither :: (Type, Type) -> Type
(Curried)Either :: Type -> Type -> Type
                   ^^^^    ^^^^^^^^^^^^
                   |       |
                   (->)    Nat (->) (->)

一个产品类别 Prod (->) (->) '(a, b) '(a', b') 不同于 不是 ((a, b) -> (a', b')):它是一个函数元组 (a -> a', b -> b') 的相同。
type Prod :: Cat k -> Cat j -> Cat (k, j)
data Prod cat1 cat2 pair pair' where
  Prod :: cat1 a a'
       -> cat2 b b'
       -> Prod cat1 cat2 '(a, b) '(a', b')

那意味着“非柯里化的双函子”是什么意思
type  UncurriedBifunctor :: ((Type, Type) -> Type) -> Constraint
class UncurriedBifunctor bi where
  bimap :: (a -> a', b -> b') -> bi '(a, b) -> bi '(a', b')

不适合任何标准的Haskell数据类型,因为它们都是柯里化的。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接