【问题标题】:Composition of two functors is a functor两个函子的组合是函子
【发布时间】:2012-12-10 12:00:16
【问题描述】:

a previous answer 中,Petr Pudlak 为 HaskHask 以外的函子定义了 CFunctor 类。使用类型族重新编写它,它看起来像

class CFunctor f where
  type Dom f :: * -> * -> *               -- domain category
  type Cod f :: * -> * -> *               -- codomain category
  cmap :: Dom f a b -> Cod f (f a) (f b)  -- map morphisms across categories

具有看起来像的实例,例如

instance CFunctor Maybe where
  type Dom Maybe = (->)                   -- domain is Hask
  type Cod Maybe = (->)                   -- codomain is also Hask 
  cmap f = \m -> case m of
                   Nothing -> Nothing
                   Just x  -> Just (f x)

在范畴论中,只要 F : C --> D 是函子且 G : D --> E 是函子,则组合 GF : C --> E 也是函子。

我想用 Haskell 来表达这一点。由于我不会写instance CFunctor (f . g),所以我引入了一个包装类:

newtype Wrap g f a = Wrap { unWrap :: g (f a) }

在编写CFunctor 实例时,我能做到

instance (CFunctor f, CFunctor g, Cod f ~ Dom g) => CFunctor (Wrap g f) where
  type Dom (Wrap g f) = Dom f
  type Cod (Wrap g f) = Cod g
  cmap = undefined

但我无法弄清楚cmap 的实现应该是什么。有什么建议吗?

PS这一切的最终原因是还引入了一个类Adjunction,其方法为unitcounit,然后自动从附件中派生monad 实例。但首先,我需要向编译器表明,两个仿函数的组合也是一个仿函数。

我知道我可以在 g (f a) 类型的对象上使用 cmap.cmap 并且这会起作用,但这看起来有点像作弊 - 肯定函子只是函子,编译器不应该有知道它实际上是另外两个函子的组合吗?

【问题讨论】:

  • 对于 Hask 上的 endofunctors,DeriveFunctor 扩展肯定会自动处理(和许多其他情况)。但是,如果您必须手动实现它,我不明白为什么您会期望比通过在每个函数上组合映射来映射组合函子更简单的方法?
  • 基本上,cmap f = cmap (cmap f) 是函子组合的定义,那么为什么要以任何方式作弊呢?
  • @CAMcCann 我很乐意将cmap = cmap . cmap 写为实现,但这对于类型构造函数Wrap g f 是不正确的(特别是,它需要Dom f a bCod g (g (f a)) (g (f b)) 而不是比Cod g (Wrap g f a) (Wrap g f b) 。如上所述,我对Hask 上的内函子以外的情况其他特别感兴趣。
  • @DanielFischer 因为g (f a)不只是两个函子的组合,它本身也是一个函子,所以我想我应该可以在它上面使用cmap(有点类似于如何使用 monad 转换器时,您不必到处写 lift . lift . lift,如果您可以告诉编译器如何自动提升某些功能 - 在这里我想告诉编译器如何使用 a 跨两个类别映射态射cmap) 的单个应用程序。
  • 我的意思是在cmap 的定义中使用Wrap f g。但是,看着它,我不明白如何为一般g 做这件事;只有Cod g = (->)才明显。

标签: haskell functor category-theory


【解决方案1】:

给定函子F : C → DG : D → E,函子组合G ∘ F : C → E 是类别CE 之间的对象映射,例如(G ∘ F)(X) = G(F(X)) 和态射之间的映射,例如(G ∘ F)(f) = G(F(f))

这表明您的CFunctor 实例应定义如下:

instance (CFunctor f, CFunctor g, Cod f ~ Dom g) => CFunctor (Wrap g f) where
  type Dom (Wrap g f) = Dom f
  type Cod (Wrap g f) = Cod g
  cmap f = cmap (cmap f)

但是,组合 cmap 两次会得到 Dom f a b -> Cod g (g (f a)) (g (f b))cmap 在这种情况下具有类型 Dom f a b -> Cod g (Wrap g f a) (Wrap g f b)

我们可以从g (f a)Wrap g f,反之亦然,但是由于实例声明没有对Cod g 的结构做出任何假设,所以我们运气不好。

既然我们知道函子是类别之间的映射,我们可以使用Cod gCategory 的事实(在Haskell 方面,这需要Category (Cod g) 约束),这给我们提供了很少的操作:

cmap f = lift? unWrap >>> cmap (cmap f) >>> lift? Wrap

然而,这需要一个方便的提升运算符lift?,它将函数从Hask 类别提升到Cod g 类别。将Cod g写成(~>)lift?的类型必须是:

lift? :: (a -> b) -> (a ~> b)

lift? unWrap  :: Wrap g f a ~> g (f a)
cmap (cmap f) :: g (f a)    ~> g (f b)
lift? Wrap    :: g (f b)    ~> Wrap g f b

lift? unWrap >>> cmap (cmap f) >>> lift? Wrap :: Wrap g f a ~> Wrap g f b

现在,这个起重操作员至少有两种选择:

  • 您可以将约束从Category (Cod g) 扩展到Arrow (Cod g),在这种情况下,起重操作员变为arr
  • 或者,正如 Sjoerd Visscher 在 cmets 中提到的那样,您可以使用 WrapunWrap 在运行时语义上为 id 的事实,在这种情况下使用 unsafeCoerce 是合理的。

【讨论】:

  • 我怀疑在存在类型族的情况下,unsafeCoerce 可能是一个糟糕的选择。诚然,还没有弄清楚任何细节。
猜你喜欢
  • 1970-01-01
  • 2016-10-23
  • 1970-01-01
  • 1970-01-01
  • 2018-01-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多