haskell 在某种意义上,'Fix'和'(,)'能被看作函子吗?

dw1jzc5e  于 2022-11-14  发布在  其他
关注(0)|答案(1)|浏览(180)

我一直在想instance Functor (f :.: g)的 * 完整的、包罗万象的 * 上下文是什么样的。我脑子里立即冒出的想法是:

newtype (f :.: g) a = Comp (f (g a))
instance (Functor f, Functor g) => Functor (f :.: g) where
    fmap f (Comp x) = Comp (fmap (fmap f) x)

但是,两个反变函子也会合成为协变的,就像这样:

instance (Contravariant f, Contravariant g) => Functor (f :.: g) where
    fmap f (Comp x) = Comp (contramap (contramap f) x)

这已经不是一个有希望的开始了。然而,我也注意到,从技术上讲,fg甚至不一定要有* -> *--对f :.: g :: * -> *的唯一要求是f :: k -> *g :: * -> k对某些k。这意味着非函子类型可以组合成函子,例如:

newtype Fix f = Fix (f (Fix f))
instance Functor (Fix :.: (,)) where
    fmap f (Comp x) = Comp (go x) where
        go (Fix (x,xs)) = Fix (f x,go xs)

Fix :.: (,)同构于Stream类型:

data Stream a = a :> Stream a

所以这看起来确实是一个不小的问题。这让我思考--如果Haskell的Functor类型类表示从Hask到Hask的范畴函子,这是否意味着像Fix(,)这样的类型可以是作用于其他范畴的函子?这些范畴会是什么?

l7mqbcuq

l7mqbcuq1#

是的,我们可以从构造函数的形状中读出它的意义,我们先来看看(,)

(,)函式

(,) :: * -> * -> *

这将接受两个类型并生成一个类型。

(,) :: (*, *) -> *

也就是说,我们可以把这个函数拆开,同时取两个参数。所以(,)可以看作是Hask × HaskHask的函子,其中Hask × Haskproduct category。我们有一个词来描述定义域是两个范畴的乘积的函子。我们称之为双函子,它实际上在base的Haskell中。具体来说,双函子p能够将乘积范畴中从(a, b)(a', b')的Map转换为从p a bp a' b'的Map。Haskell的类型类以稍微不同但等效的方式编写此函数

bimap :: Bifunctor p => (a -> b) -> (c -> d) -> p a c -> p b d

拥有一个Mapa -> b和一个Mapc -> d完全等价于拥有一个Map(a, c) -> (b, d) * 在产品类别 * 中。(我的意思是:产品类别中的MapX1 M15 N1 X被 * 定义 * 为MapX1 M16 N1 X和X1 M17 N1 X的产品)。

Fix函数

我们可以用同样的方法处理Fix

newtype Fix f = Fix (f (Fix f))

它的形状是

Fix :: (* -> *) -> *

它接受一个单参数类型构造函数并生成一个类型。
现在,在Haskell中,* -> *部分可以是 * 任何 * 单参数类型的构造函数,但是绝对地说,使用函子要 * 好 * 得多。(我们很快就会用到),Fix* -> *参数是Functor,也就是从HaskHask的函子。
在这种情况下,Fix就有了从functor categoryHask ^ Hask到范畴Hask的函子的正确形状。函子,绝对地,把对象带到对象,把箭头带到箭头,所以让我们一步一步地来。
对象部分很简单,我们已经定义过了。具体来说,Fix取函子f(函子是函子范畴的 * 对象 *;如果还没有意义,请再读一遍),并将其Map到我们刚刚定义的类型Fix f
现在,函子范畴的 * 箭头 * 是natural transformations。给定两个函子f, g :: C -> D,从fg的自然变换α是从C的 * 对象 * 到D的 * 箭头 * 的Map。具体地,对于范畴C中的每个对象xα x应该是D中从f xg x的箭头,具有以下相干条件:
对于C中的每个箭头h : x -> y,我们必须有(g h) . (α x) === (α y) . (f h)
(使用符号如函数组合 * 非常 * 松散,符合真正的范畴论精神)
如图所示,下面必须画一个可交换图,

Haskell实际上并没有一个用于自然变换的内置类型。

(forall a. f a -> g a)

这是一个自然变换的形状,当然我们还没有验证它的相干性,所以我们只能相信它满足这个性质。
记住这些抽象的废话,如果Fix是从Hask ^ HaskHask的函子,它应该自然地变换成一个普通的 haskell 函数,它应该有下面的形状。

fixmap :: (Functor f, Functor g) => (forall a. f a -> g a) -> Fix f -> Fix g

一旦我们有了这个类型,我们就可以很容易地编写实现了。

fixmap h (Fix inner) = Fix (h . fmap (fixmap h) $ inner)

或者,等价地(根据自然变换的规则),

fixmap h (Fix inner) = Fix (fmap (fixmap h) . h $ inner)

我不知道这种形式的函子有什么惯用的名字,也不知道有什么类型类包含它,但当然没有什么能阻止你自己创建它。

相关问题