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,但我们暂时将其搁置一旁。为清楚起见,
Hask
的对象是具体类型,即种类*
的类型。这包括像 Int -> [Char]
这样的函数类型,但不包括像 Maybe :: * -> *
这样需要类型参数的任何东西。但是,具体类型 Maybe Int :: *
属于 Hask
。类型构造函数/多态函数更像是自然的 t运行sformations(或其他更通用的从 Hask
到自身的映射),而不是态射。
Hask
的态射是 Haskell 函数。对于两个具体类型 A
和 B
,hom 集 Hom(A,B)
是具有签名 A -> B
. 的函数集
- 函数组成由
f . g
给出。如果我们担心严格性,我们可能 redefine composition to be strict or be careful about defining equivalence classes 个函数。
Functor
s 是 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
对应于从 Hask
到 Hask
的函子,如下所示:
对于Hask
中的每个具体类型a
,我们分配具体类型Maybe a
给Hask
中的每个态射f :: A -> B
,我们分配发送Nothing ↦ Nothing
和Just x ↦ Just (f x)
的态射Maybe A -> Maybe B
。
常量 (endo)Functor
类别C上的constant (endo)functor是一个函子Δc : C → C
将类别C的每个对象映射到固定对象c∈C
并将C的每个态射映射到恒等态射id_c : c → c
为固定对象。
Const
Functor
考虑 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
、
的实现中
- 在左侧,
Const v
显示为 Konst m a
,由于多态性 ,这没问题
- 在右侧,
Const v
显示为 Konst m b
,由于多态性 ,这没问题
但如果我们试图将 Konst m :: * -> *
视为范畴论意义上的常数函子,我的理解就会崩溃。
什么是固定对象?类型构造函数 Konst m
采用一些具体类型 a
并给我们一个 Konst m a
,至少从表面上看,它是每个 a
的不同具体类型。我们真的很想将每个类型 a
映射到固定类型 m
.
根据类型签名,fmap
接受一个 f :: a -> b
并给我们一个 Konst m a -> Konst m b
。如果 Konst m
类似于常数函子,fmap
将需要将每个态射发送到固定类型 m
.
上的恒等态射 id :: m -> m
问题
所以,这是我的问题:
Haskell 的 Const
函子在哪些方面类似于范畴论中的常数函子?
如果这两个概念不等价,是否有可能在 Haskell 代码中表达范畴论常数函子(称之为 SimpleConst
,比方说)?我快速尝试了一下,运行 遇到了与上述幻像类型多态性相同的问题:
data SimpleKonst a = SimpleConst Int
instance Functor SimpleConst where
fmap :: (a -> b) -> SimpleConst a -> SimpleConst b
fmap _ (SimpleConst x) = (SimpleConst x)
如果#2 的答案是肯定的,如果是,那么这两个 Haskell 函数在范畴论意义上有何关联?也就是说,SimpleConst
是 Haskell 中的 Const
就像常数函子是范畴论中的 __?__
?
幻像类型是否会给将 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 a
到 Konst m b
的转换,除了挥手说“存在双射” !".
相关参考资料
以下是上面尚未链接的可能相关问题/参考的列表:
- 计算器溢出,"Do all Type Classes in Haskell have a Category-Theoretic Analogue?"
- 计算器溢出,"How are functors in Haskell related to functors in category theory?"
- 维基百科,Haskell/Category Theory
你是对的,Konst m
完全 从范畴论的角度来看不是常数函子。但它与一个密切相关!
type CF m a = m
现在(CF m, id @m)
真的是一个常量函子。我认为主要的教训是,虽然我们将 Functor
视为 Hask
上的内函子的 class,但实际上并不是所有内函子。
我不认为幻像类型本身是个问题。 Konst m a
和 Konst m b
是不同但同构的对象。
- 问。 Haskell 的 Const 函子与范畴论中的常数函子有何相似之处?
A. Const a b
将 ant 类型 b
发送到与 a
同构的类型,而不是像类别理论定义所要求的那样将其发送到 a
。这没什么大不了的,因为同构对象“实际上是同一个对象”。
- 问。如果这两个概念不等价,是否有可能在 Haskell 代码中表达范畴论常数函子(称之为
SimpleConst
,比方说)?
A. 它们不完全等价,但它们等价到同构,这就足够了。如果你想要精确的等价,那么它取决于你所说的“在代码中表达仿函数”的确切含义。让我们考虑恒等函子,因为它比 Const 简单一些。你可以只写一个类型别名:type Id a = a
,关联的态射就是 id
。如果你想为这个 Id
写一个 Functor
的实例,那么不行,你不能在 Haskell 中这样做,因为你不能为类型别名写 class 实例(也许你可以用其他一些类似的语言来做到这一点)。
- 问。 [I]这两个 Haskell 函数在类别理论意义上是如何相关的?
A、范畴论中没有the const functor。每个对象都有 a 常量仿函数。 Haskell Const a
与与对象 a
关联的 a const 仿函数有关。 Haskell Const
(无参数)确实是双函子(如果我没记错的话,是正确的投影双函子)。
- 问。幽灵类型是否会给将 Hask 视为一个类别带来问题?我们是否需要修改 Hask 的定义,使对象真正等价 class 类型,如果不存在幻影类型参数,这些类型将是相同的?
A. 不,这不是问题。自然同构函子(或 Functor
s)“本质上是相同的”。我们在范畴论中说“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 Char
和 Konst 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)
我知道 Haskell 中的许多名称都受到类别理论术语的启发,我正在尝试准确理解类比的开始和结束位置。
类别Hask
由于有关 strictness/laziness 和 seq
的一些技术细节,我已经知道 Hask
is not (necessarily) a category,但我们暂时将其搁置一旁。为清楚起见,
Hask
的对象是具体类型,即种类*
的类型。这包括像Int -> [Char]
这样的函数类型,但不包括像Maybe :: * -> *
这样需要类型参数的任何东西。但是,具体类型Maybe Int :: *
属于Hask
。类型构造函数/多态函数更像是自然的 t运行sformations(或其他更通用的从Hask
到自身的映射),而不是态射。Hask
的态射是 Haskell 函数。对于两个具体类型A
和B
,hom 集Hom(A,B)
是具有签名A -> B
. 的函数集
- 函数组成由
f . g
给出。如果我们担心严格性,我们可能 redefine composition to be strict or be careful about defining equivalence classes 个函数。
Functor
s 是 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
对应于从 Hask
到 Hask
的函子,如下所示:
对于
Hask
中的每个具体类型a
,我们分配具体类型Maybe a
给
Hask
中的每个态射f :: A -> B
,我们分配发送Nothing ↦ Nothing
和Just x ↦ Just (f x)
的态射Maybe A -> Maybe B
。
常量 (endo)Functor
类别C上的constant (endo)functor是一个函子Δc : C → C
将类别C的每个对象映射到固定对象c∈C
并将C的每个态射映射到恒等态射id_c : c → c
为固定对象。
Const
Functor
考虑 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
、
- 在左侧,
Const v
显示为Konst m a
,由于多态性 ,这没问题
- 在右侧,
Const v
显示为Konst m b
,由于多态性 ,这没问题
但如果我们试图将 Konst m :: * -> *
视为范畴论意义上的常数函子,我的理解就会崩溃。
什么是固定对象?类型构造函数
Konst m
采用一些具体类型a
并给我们一个Konst m a
,至少从表面上看,它是每个a
的不同具体类型。我们真的很想将每个类型a
映射到固定类型m
.根据类型签名,
上的恒等态射fmap
接受一个f :: a -> b
并给我们一个Konst m a -> Konst m b
。如果Konst m
类似于常数函子,fmap
将需要将每个态射发送到固定类型m
.id :: m -> m
问题
所以,这是我的问题:
Haskell 的
Const
函子在哪些方面类似于范畴论中的常数函子?如果这两个概念不等价,是否有可能在 Haskell 代码中表达范畴论常数函子(称之为
SimpleConst
,比方说)?我快速尝试了一下,运行 遇到了与上述幻像类型多态性相同的问题:
data SimpleKonst a = SimpleConst Int
instance Functor SimpleConst where
fmap :: (a -> b) -> SimpleConst a -> SimpleConst b
fmap _ (SimpleConst x) = (SimpleConst x)
如果#2 的答案是肯定的,如果是,那么这两个 Haskell 函数在范畴论意义上有何关联?也就是说,
SimpleConst
是 Haskell 中的Const
就像常数函子是范畴论中的__?__
?幻像类型是否会给将
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 a
到 Konst m b
的转换,除了挥手说“存在双射” !".
相关参考资料
以下是上面尚未链接的可能相关问题/参考的列表:
- 计算器溢出,"Do all Type Classes in Haskell have a Category-Theoretic Analogue?"
- 计算器溢出,"How are functors in Haskell related to functors in category theory?"
- 维基百科,Haskell/Category Theory
你是对的,Konst m
完全 从范畴论的角度来看不是常数函子。但它与一个密切相关!
type CF m a = m
现在(CF m, id @m)
真的是一个常量函子。我认为主要的教训是,虽然我们将 Functor
视为 Hask
上的内函子的 class,但实际上并不是所有内函子。
我不认为幻像类型本身是个问题。 Konst m a
和 Konst m b
是不同但同构的对象。
- 问。 Haskell 的 Const 函子与范畴论中的常数函子有何相似之处?
A.Const a b
将 ant 类型b
发送到与a
同构的类型,而不是像类别理论定义所要求的那样将其发送到a
。这没什么大不了的,因为同构对象“实际上是同一个对象”。 - 问。如果这两个概念不等价,是否有可能在 Haskell 代码中表达范畴论常数函子(称之为
SimpleConst
,比方说)?
A. 它们不完全等价,但它们等价到同构,这就足够了。如果你想要精确的等价,那么它取决于你所说的“在代码中表达仿函数”的确切含义。让我们考虑恒等函子,因为它比 Const 简单一些。你可以只写一个类型别名:type Id a = a
,关联的态射就是id
。如果你想为这个Id
写一个Functor
的实例,那么不行,你不能在 Haskell 中这样做,因为你不能为类型别名写 class 实例(也许你可以用其他一些类似的语言来做到这一点)。 - 问。 [I]这两个 Haskell 函数在类别理论意义上是如何相关的?
A、范畴论中没有the const functor。每个对象都有 a 常量仿函数。 HaskellConst a
与与对象a
关联的 a const 仿函数有关。 HaskellConst
(无参数)确实是双函子(如果我没记错的话,是正确的投影双函子)。 - 问。幽灵类型是否会给将 Hask 视为一个类别带来问题?我们是否需要修改 Hask 的定义,使对象真正等价 class 类型,如果不存在幻影类型参数,这些类型将是相同的?
A. 不,这不是问题。自然同构函子(或Functor
s)“本质上是相同的”。我们在范畴论中说“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 Char
和 Konst 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)