协数据类型真的是终结代数吗?

Syl*_*ert 13 haskell type-systems category-theory coinduction

(免责声明:我不是 100% 确定 codatatype 是如何工作的,特别是在不涉及终端代数时)。

考虑“类型类别”,类似于Hask,但进行了适合讨论的任何调整。在这样的类别中,据说(1)初始代数定义数据类型,(2)终端代数定义协数据类型。

我正在努力说服自己相信(2)。

考虑函子T(t) = 1 + a * t。我同意初始T代数是明确定义的并且确实定义了[a], 的列表a。根据定义,初始的T-algebra 是一个类型X和一个函数f :: 1+a*X -> X,这样对于任何其他类型Y和函数g :: 1+a*Y -> Y,都存在一个m :: X -> Y这样的函数m . f = g . T(m)(其中.表示 Haskell 中的函数组合运算符)。通过f解释为列表构造函数、g初始值和阶跃函数以及T(m)递归操作,该方程本质上断言了m给定任何初始值和 中定义的任何阶跃函数的函数的唯一存在性g,这需要一个底层的良好-fold与基础类型(. 列表)一起表现a

例如,g :: Unit + (a, Nat) -> Nat可以是() -> 0 | (_,n) -> n+1,在这种情况下m定义长度函数,或者g可以是() -> 0 | (_,n) -> 0,然后m定义常数零函数。这里的一个重要事实是,无论什么gm总是可以唯一定义,就像fold不对其参数施加任何限制并始终产生唯一的明确定义的结果一样。

这似乎不适用于终端代数。

考虑上面定义的相同函子T。终末代数的定义T与初始代数相同,只是m现在 是 类型X -> Y并且方程现在变为m . g = f . T(m)。据说这应该定义一个潜在的无限列表。

我同意这有时是正确的。例如,当g :: Unit + (Unit, Int) -> Int被定义为() -> 0 | (_,n) -> n+1像之前一样,m那么 的行为使得m(0) = ()m(n+1) = Cons () m(n)。对于非负nm(n)应该是一个有限的单位列表。对于任何负数nm(n)应该具有无限长度。可以验证上式对于这样的g和成立m

然而,对于以下两个修改后的定义中的任何一个g,我都看不到任何明确的定义m了。

首先,当g再次为() -> 0 | (_,n) -> n+1且类型为时g :: Unit + (Bool, Int) -> Intm必须满足m(g((b,i))) = Cons b m(g(i)),这意味着结果取决于b。但这是不可能的,因为m(g((b,i)))实际上只是m(i+1)其中没有提及b任何内容,因此方程没有明确定义。

其次,当g再次为 类型g :: Unit + (Unit, Int) -> Int但被定义为常数零函数时g _ = 0m必须满足m(g(())) = Nilm(g(((),i))) = Cons () m(g(i)),这是矛盾的,因为它们的左侧相同,均为m(0),而右侧则永远不同。

总之,存在T与假定的终端代数没有态射的 代数T,这意味着终端T代数不存在。协同数据类型 Stream (或无限列表)的理论建模(如果有的话)不能基于函子 的不存在的终端代数T(t) = 1 + a * t

非常感谢上述故事中任何缺陷的暗示。

And*_*ács 15

(2) 终结代数定义了辅助数据类型。

这是不对的,codatatypes 是终端余代数。对于你的函子来说,余代数是一种与 一起的T类型。和之间的-代数态射是这样的。使用这个定义,终结代数定义了可能无限的列表(所谓的“colists”),并且终结性由函数见证:xf :: x -> T xT(x1, f1)(x2, f2)g :: x1 -> x2fmap g . f1 = f2 . gTunfold

unfold :: (x -> Unit + (a, x)) -> x -> Colist a
Run Code Online (Sandbox Code Playgroud)

请注意,终端T- 代数确实存在:它只是Unit类型和常量函数T Unit -> Unit(这可以作为任何 的终端代数T)。但这对于编写程序来说并不是很有趣。


Li-*_*Xia 8

据说 (1) 初始代数定义数据类型,(2) 终端代数定义辅助数据类型。

关于第二点,实际上是说终端余代数定义了协数据类型。

数据类型t由其构造函数和折叠定义。

  • 构造函数可以通过代数建模F t -> t(例如,Peano 构造函数O : nat S : Nat -> Nat被收集为单个函数in : Unit + Nat -> Nat)。
  • fold f : t -> x然后,折叠给出任何代数的变形f : F x -> x(对于 nats,fold : ((Unit + x) -> x) -> Nat -> x)。

协同数据类型t由其析构函数和展开函数定义。

  • 析构函数可以通过余代数来建模t -> F t(例如,流有两个析构函数head : Stream a -> atail : Stream a -> Stream a,并且它们被收集为单个函数out : Stream a -> a * Stream a)。
  • 然后展开给出unfold f : x -> t任何代数的变形f : x -> F x(对于流,unfold : (x -> a * x) -> x -> Stream a)。


Jon*_*rdy 8

\n

(免责声明:我不是 100% 确定 codatatype 是如何工作的,特别是在不涉及终端代数时)。

\n
\n

协同数据类型(codata type),或共归纳数据类型,只是一种通过其消除而不是通过引入来定义的类型。

\n

似乎有时使用终端代数(非常令人困惑)来指代最终的 coalgebra,这实际上定义了 codata 类型。

\n
\n

考虑上面定义的相同函子 T。终端 T 代数的定义与初始代数相同,只是 m 现在是类型 X -> Y 并且方程现在变为 m 。g = f 。Tm值)。据说这应该定义一个潜在的无限列表。

\n
\n

所以我认为这就是你\xe2\x80\x99出错的地方: \xe2\x80\x9c m \xe2\x88\x98 g = f \xe2\x88\x98 T ( m )\xe2\x80\x9d 应该是反转,读取 \xe2\x80\x9c T ( m ) \xe2\x88\x98 f = g \xe2\x88\x98 m \xe2\x80\x9d。也就是说,最终的余代数由载体集S和映射g : S \xe2\x86\x92 T ( S ) 定义,使得对于任何其他余代数 ( R , f : R \xe2\x86\x92 T ( R )) 存在唯一映射m : R \xe2\x86\x92 S使得T ( m ) \xe2\x88\x98 f = g \xe2\x88\x98 m

\n

m是由映射唯一递归定义的,Left ()每当f映射到Left (),并且每当f映射到Right (x, m xs)时,它都会返回,即 it\xe2\x80\x99s 将余代数分配给最终余代数的唯一态射,并表示唯一的变形/展开这种类型的流,应该很容易让自己相信,它实际上是一个可能为空且可能为无限的流。Right (x, xs)

\n