Haskell 的 `Const` 函子是否类似于范畴论中的常数函子?

Is Haskell's `Const` Functor analogous to the constant functor from category theory?

我知道 Haskell 中的许多名称都受到类别理论术语的启发,我正在尝试准确理解类比的开始和结束位置。

类别Hask

由于有关 strictness/laziness 和 seq 的一些技术细节,我已经知道 Hask is not (necessarily) a category,但我们暂时将其搁置一旁。为清楚起见,

Functors 是 Hask

中的内函子

我不认为上面的技术细节与我下面的困惑有任何关系。我想我明白every instance of Functor is an endofunctor in the category Hask的意思了。也就是说,如果我们有

class Functor (F :: * -> *) where
    fmap :: (a -> b) -> F a -> F b

-- Maybe sends type T to (Maybe T) 
data Maybe a = Nothing | Just a

instance Functor Maybe where
  fmap f (Just x) = Just (f x)
  fmap _ Nothing  = Nothing

Functor 实例 Maybe 对应于从 HaskHask 的函子,如下所示:

常量 (endo)Functor

类别C上的constant (endo)functor是一个函子Δc : C → C将类别C的每个对象映射到固定对象c∈C并将C的每个态射映射到恒等态射id_c : c → c为固定对象。

ConstFunctor

考虑 Data.Functor.Const。为了清楚起见,我将在这里重新定义它,区分类型构造函数Konst :: * -> * -> *和数据构造函数Const :: forall a,b. a -> Konst a b

newtype Konst a b = Const { getConst :: a }

instance Functor (Konst m) where
    fmap :: (a -> b) -> Konst m a -> Konst m b
    fmap _ (Const v) = Const v

此类型检查是因为数据构造函数 Const 是多态的:

       v  :: a
(Const v) :: forall b. Konst a b

我可以相信 Konst m 是类别 Hask 中的内函子,因为在 fmap

的实现中

但如果我们试图将 Konst m :: * -> * 视为范畴论意义上的常数函子,我的理解就会崩溃。

问题

所以,这是我的问题:

  1. Haskell 的 Const 函子在哪些方面类似于范畴论中的常数函子?

  2. 如果这两个概念不等价,是否有可能在 Haskell 代码中表达范畴论常数函子(称之为 SimpleConst,比方说)?我快速尝试了一下,运行 遇到了与上述幻像类型多态性相同的问题:

data SimpleKonst a = SimpleConst Int

instance Functor SimpleConst where
    fmap :: (a -> b) -> SimpleConst a -> SimpleConst b
    fmap _ (SimpleConst x) = (SimpleConst x)
  1. 如果#2 的答案是肯定的,如果是,那么这两个 Haskell 函数在范畴论意义上有何关联?也就是说,SimpleConst 是 Haskell 中的 Const 就像常数函子是范畴论中的 __?__?

  2. 幻像类型是否会给将 Hask 视为类别带来问题?我们是否需要修改 Hask 的定义,使对象真正等价 类 类型,如果不存在幻像类型参数,则这些类型是相同的?

编辑:自然同构?

看起来多态函数 getConst :: forall a,b. Konst a b -> a 是从函子 Konst m 到常数函子 Δm : Hask → Hask 的自然同构 η : (Konst m) ⇒ Δm 的候选者,尽管我还没有尚未能够确定后者是否可以在 Haskell 代码中表达。

自然的 t运行 形成规律是 η_x = (Konst m f) . η_y。我无法证明这一点,因为我不确定如何正式推理 (Const v) 从类型 Konst m aKonst m b 的转换,除了挥手说“存在双射” !".

相关参考资料

以下是上面尚未链接的可能相关问题/参考的列表:

你是对的,Konst m 完全 从范畴论的角度来看不是常数函子。但它与一个密切相关!

type CF m a = m

现在(CF m, id @m)真的是一个常量函子。我认为主要的教训是,虽然我们将 Functor 视为 Hask 上的内函子的 class,但实际上并不是所有内函子。

我不认为幻像类型本身是个问题。 Konst m aKonst m b 是不同但同构的对象。

  1. 问。 Haskell 的 Const 函子与范畴论中的常数函子有何相似之处?
    A. Const a b 将 ant 类型 b 发送到与 a 同构的类型,而不是像类别理论定义所要求的那样将其发送到 a。这没什么大不了的,因为同构对象“实际上是同一个对象”。
  2. 问。如果这两个概念不等价,是否有可能在 Haskell 代码中表达范畴论常数函子(称之为 SimpleConst,比方说)?
    A. 它们不完全等价,但它们等价到同构,这就足够了。如果你想要精确的等价,那么它取决于你所说的“在代码中表达仿函数”的确切含义。让我们考虑恒等函子,因为它比 Const 简单一些。你可以只写一个类型别名:type Id a = a,关联的态射就是 id。如果你想为这个 Id 写一个 Functor 的实例,那么不行,你不能在 Haskell 中这样做,因为你不能为类型别名写 class 实例(也许你可以用其他一些类似的语言来做到这一点)。
  3. 问。 [I]这两个 Haskell 函数在类别理论意义上是如何相关的?
    A、范畴论中没有the const functor。每个对象都有 a 常量仿函数。 Haskell Const a 与与对象 a 关联的 a const 仿函数有关。 Haskell Const(无参数)确实是双函子(如果我没记错的话,是正确的投影双函子)。
  4. 问。幽灵类型是否会给将 Hask 视为一个类别带来问题?我们是否需要修改 Hask 的定义,使对象真正等价 class 类型,如果不存在幻影类型参数,这些类型将是相同的?
    A. 不,这不是问题。自然同构函子(或 Functors)“本质上是相同的”。我们在范畴论中说“ConstX 函子将任何对象发送到 X”,但我们也可以选择自然同构于 ConstX 的任何函子来研究它。它不会以任何有意义的方式改变我们的数学。我们选择 ConstX 只是因为它是最容易定义的。在 Haskell 中,Const X 是最容易定义的,所以我们使用它作为我们的 the 常数函子。

附录。

constIso1 :: Konst x a -> x
constIso1 (Const x) = x
constIso2 :: x -> Konst x a
constIso2 = Const

我们这里的问题是,从数学上讲,函子是一对依赖对,但它是一方(Type -> Type 映射)存在于 Haskell 类型级世界中的一对,而另一侧((a -> b) -> f a -> f b 映射)生活在价值层面的世界中。 Haskell 没有办法表达这样的对。 Functor class 通过只允许 类型的构造函数 作为 Type -> Type 映射来绕过这个限制。
这有帮助的原因是类型构造器是唯一的,即它们中的每一个都可以通过 Haskell 的类型class 机制分配一个明确定义的态射映射。但不利的一面是,结果总是唯一的,所以你最终会遇到 Konst Int CharKonst Int Bool 在技术上不同的情况,尽管是同构的类型。

一种更数学化的表达函子的方法需要一种单独的方法来在类型级别识别您所指的函子。那么你只需要一个类型级别的映射(可以用类型族来完成)和一个类型→值级别的映射(typeclass):

type family FunctorTyM f a :: Type
class FunctorMphM f where
  fmap' :: (a -> b) -> FunctorTyM f a -> FunctorTyM f b

data KonstFtor a
type instance FunctorTyM (KonstFtor a) b = a
instance FunctorMphM (KonstFtor a) where
  fmap' _ = id

这仍然允许您拥有标准仿函数的实例:

data IdentityFtor
type instance FunctorTyM IdentityFtor a = a
instance FunctorMphM IdentityFtor where
  fmap' f = f

data ListFtor
type instance FunctorTyM ListFtor a = [a]
instance FunctorMphM ListFtor where
  fmap' f = map f

但是实际使用起来会比较别扭。你会注意到 FunctorMphM class 需要 -XAllowAmbiguousTypes 才能编译——那是因为 f 不能从 FunctorTyM f 推断出来。 (我们可以用单射类型族来改善这个问题,但这只会让我们回到我们开始时遇到的同一个问题:问题恰恰是 const 函子是 not 单射的!)

对于现代 Haskell,这 没问题 不过,这只是意味着您需要明确说明您正在使用的函子。 (可以说,这通常是一件 好事 的事情!)完整示例:

{-# LANGUAGE TypeFamilies, AllowAmbiguousTypes, TypeApplications #-}

type family FunctorTyM f a
class FunctorMphM f where ...

data KonstFtor a
...

data IdentityFtor
...

data ListFtor
...

main :: IO ()
main = do
  print (fmap' @(KonstFtor Int) (+2) 5)
  print (fmap' @IdentityFtor (+2) 5)
  print (fmap' @ListFtor (+2) [7,8,9])

输出:

5
7
[9,10,11]

这种方法还有其他一些优点。例如,我们最终可以表达元组在其参数的 each 中是函子,而不仅仅是在最右边的一个:

data TupleFstConst a
type instance FunctorTyM (TupleFstConst a) b = (a,b)
instance FunctorMphM (TupleFstConst a) where
  fmap' f (x,y) = (x, f y)

data TupleSndConst b
type instance FunctorTyM (TupleSndConst b) a = (a,b)
instance FunctorMphM (TupleSndConst b) where
  fmap' f (x,y) = (f x, y)

data TupleFtor
type instance FunctorTyM TupleFtor a = (a,a)
instance FunctorMphM TupleFtor where
  fmap' f (x,y) = (f x, f y)

main :: IO ()
main = do
  print (fmap' @(TupleFstConst Int) (+2) (5,50))
  print (fmap' @(TupleSndConst Int) (+2) (5,50))
  print (fmap' @TupleFtor           (+2) (5,50))
(5,52)
(7,50)
(7,52)