与箭头相比,profunctor 缺乏的是组合它们的能力。如果我们添加合成,我们会得到一个箭头吗?
类群
这正是“Notions of Computation as Monoids”的第 6 节中解决的问题,它解开了(相当密集的)“Categorical semantics for arrows”的结果。 “概念”是一篇很棒的论文,因为虽然它深入探讨了范畴论,但它 (1) 并没有假设读者对抽象代数有粗略的了解,并且 (2) 用 Haskell 代码说明了大多数诱发偏头痛的数学。我们可以在这里简要总结论文的第 6 部分:
说我们有
class Profunctor p where
dimap :: (contra' -> contra) -> (co -> co') -> p contra co -> p contra' co'
您在 Haskell 中对 profunctor 的沼泽标准、正负除法编码。现在这个数据类型,
data (⊗) f g contra co = forall x. (f contra x) ⊗ (g x co)
正如在Data.Profunctor.Composition 中实现的那样,其作用类似于profunctor 的组合。例如,我们可以证明一个合法的实例Profunctor:
instance (Profunctor f, Profunctor g) => Profunctor (f ⊗ g) where
dimap contra co (f ⊗ g) = (dimap contra id f) ⊗ (dimap id co g)
出于时间和空间的原因,我们将举手证明它是合法的。
好的。现在有趣的部分。说我们这个类型类:
class Profunctor p => ProfunctorMonoid p where
e :: (a -> b) -> p a b
m :: (p ⊗ p) a b -> p a b
这是一种在 Haskell 中编码 profunctor monoids 概念的方式。具体来说,这是幺半群Pro 中的幺半群,它是函子类别[C^op x C, Set] 的幺半群结构,⊗ 作为张量,Hom 作为其单位。所以这里有很多超具体的数学术语需要解压,但你应该阅读这篇论文。
然后我们看到ProfunctorMonoid 与Arrow 同构......几乎。
instance ProfunctorMonoid p => Category p where
id = dimap id id
(.) pbc pab = m (pab ⊗ pbc)
instance ProfunctorMonoid p => Arrow p where
arr = e
first = undefined
instance Arrow p => Profunctor p where
lmap = (^>>)
rmap = (>>^)
instance Arrow p => ProfunctorMonoid p where
e = arr
m (pax ⊗ pxb) = pax >> pxb
当然,我们在这里忽略了类型类定律,但正如论文所示,它们确实非常有效。
现在我说几乎是因为我们无法实现first。我们真正所做的是证明了ProfunctorMonoid 和pre-arrows 之间的同构。论文称Arrow 没有first 一个pre-arrow。然后它继续表明
class Profunctor p => StrongProfunctor p where
first :: p x y -> p (x, z) (y, z)
class StrongProfunctor p => StrongProfunctorMonoid p where
e :: (a -> b) -> p a b
m :: (p ⊗ p) a b -> p a b
对于Arrow 的所需同构是必要且充分的。 “强”这个词来自范畴论中的一个特定概念,这篇论文以比我所能想到的更好的文字和更丰富的细节来描述。
总结一下:
profunctor 类别中的幺半群是前箭头,反之亦然。 (该论文的先前版本使用术语“弱箭头”而不是预箭头,我想这也可以。)
强仿函数类别中的幺半群是箭头,反之亦然。
由于 monad 是内函子范畴中的幺半群,我们可以将其与 SAT 类比 Functor : Profunctor :: Monad : Arrow。这是计算概念作为幺半群论文的真正主旨。
Monoids 和 monoidal 类别是随处可见的温和海洋生物,有些学生会在没有学习过 monoids 的情况下接受计算机科学或软件工程教育,这是一种耻辱。
范畴论很有趣。
Haskell 很有趣。