[英]Are codatatypes really terminal algebras?
(免责声明:我不是 100% 确定 codatatype 是如何工作的,尤其是在不涉及终端代数时)。
考虑“类型的类别”,类似于Hask ,但可以进行任何适合讨论的调整。 在这样一个类别中,据说(1)初始代数定义数据类型,(2)终端代数定义余数据类型。
我正在努力说服自己相信(2)。
考虑函子T(t) = 1 + a * t
。 我同意最初的T
代数是明确定义的,并且确实定义了[a]
,即a
的列表。 根据定义,初始T
代数是类型X
和 function f:: 1+a*X -> X
,因此对于任何其他类型Y
和 function g:: 1+a*Y -> Y
,有正好是一个 function m:: X -> Y
使得m. f = g. T(m)
m. f = g. T(m)
m. f = g. T(m)
(其中.
表示在 Haskell 中的 function 组合运算符)。 With f
interpreted as the list constructor(s), g
the initial value and the step function, and T(m)
the recursion operation, the equation essentially asserts the unique existance of the function m
given any initial value and any step function defined in g
,这需要一个行为良好的基础fold
以及基础类型,即a
的列表。
例如, g:: Unit + (a, Nat) -> Nat
可以是() -> 0 | (_,n) -> n+1
() -> 0 | (_,n) -> n+1
,在这种情况下m
定义长度 function,或者g
可以是() -> 0 | (_,n) -> 0
() -> 0 | (_,n) -> 0
,然后m
定义一个常数零 function。 这里的一个重要事实是,对于任何g
, m
始终可以唯一定义,就像fold
不会对其 arguments 施加任何约束,并且始终会产生唯一的明确定义的结果。
这似乎不适用于终端代数。
考虑上面定义的相同的仿函数T
终端T
代数的定义与初始代数相同,不同之处在于m
现在是X -> Y
类型,方程现在变为m. g = f. T(m)
m. g = f. T(m)
m. g = f. T(m)
。 据说这应该定义一个潜在的无限列表。
我同意这有时是真的。 例如,当g:: Unit + (Unit, Int) -> Int
定义为() -> 0 | (_,n) -> n+1
() -> 0 | (_,n) -> n+1
像以前一样,然后m
的行为使得m(0) = ()
和m(n+1) = Cons () m(n)
。 对于非负n
, m(n)
应该是一个有限的单元列表。 对于任何负数n
, m(n)
应该是无限长的。 可以验证上面的等式对于这样的g
和m
成立。
但是,对于以下两个修改后的g
定义中的任何一个,我再也看不到任何定义明确的m
了。
首先,当g
再次为() -> 0 | (_,n) -> n+1
() -> 0 | (_,n) -> n+1
但属于g:: Unit + (Bool, Int) -> Int
类型, m
必须满足m(g((b,i))) = Cons bm(g(i))
,这意味着结果取决于b
。 但这是不可能的,因为m(g((b,i)))
实际上只是m(i+1)
没有提到b
,所以方程没有明确定义。
其次,当g
再次是g:: Unit + (Unit, Int) -> Int
类型但被定义为常数零 function g _ = 0
时, m
必须满足m(g(())) = Nil
和m(g(((),i))) = Cons () m(g(i))
,这是矛盾的,因为它们的左侧是相同的,都是m(0)
,而右侧永远不是相同的。
总之,有T
-代数没有态射到假设的终端T
-代数中,这意味着终端T
-代数不存在。 如果有的话,codatatype Stream(或无限列表)的理论建模不能基于函子T(t) = 1 + a * t
的不存在终端代数。
非常感谢上面故事中任何缺陷的暗示。
(2) 终结代数定义了余数据类型。
这是不对的, codatatypes 是终端colgebras 。 对于您的T
函子,余代数是x
和f:: x -> T x
的类型。 (x1, f1)
和(x2, f2)
之间的T
-coalgebra 态射是g:: x1 -> x2
使得fmap g. f1 = f2. g
fmap g. f1 = f2. g
fmap g. f1 = f2. g
。 使用这个定义,终端T
-代数定义了可能的无限列表(所谓的“colists”),并且终端性由unfold
function 见证:
unfold :: (x -> Unit + (a, x)) -> x -> Colist a
请注意,尽管确实存在终端T
代数:它只是Unit
类型以及常数 function T Unit -> Unit
(这可以作为任何T
的终端代数)。 但这对于编写程序并不是很有趣。
据说(1)初始代数定义数据类型,(2)终端代数定义余数据类型。
关于第二点,实际上据说终端余代数定义了余数据类型。
数据类型t
由其构造函数和折叠定义。
F t -> t
来建模(例如,Peano 构造函数O: nat
S: Nat -> Nat
被收集为单个 function in: Unit + Nat -> Nat
)。fold f: t -> x
对于任何代数f: F x -> x
(对于 nats, fold: ((Unit + x) -> x) -> Nat -> x
)。 codatatype t
由它的析构函数和展开定义。
t -> F t
(for example, streams have two destructors head: Stream a -> a
and tail: Stream a -> Stream a
, and they are collected as a single function out: Stream a -> a * Stream a
)。unfold f: x -> t
对于任何余代数f: x -> F x
(对于流, unfold: (x -> a * x) -> x -> Stream a
)。(免责声明:我不是 100% 确定 codatatype 是如何工作的,尤其是在不涉及终端代数时)。
余数据类型或协导数据类型只是由它的消除而不是它的引入来定义的。
似乎有时使用终端代数(非常令人困惑)来指代最终的 colgebra ,这实际上定义了余数据类型。
考虑上面定义的相同的仿函数 T。 终端 T 代数的定义与初始代数相同,不同之处在于 m 现在是 X -> Y 类型,方程现在变为 m。 g = f。 Tm值)。 据说这应该定义一个潜在的无限列表。
所以我认为这是你出错的地方:“ m ∘ g = f ∘ T ( m )”应该颠倒过来,改为“ T ( m ) ∘ f = g ∘ m ”。 That is, the final coalgebra is defined by a carrier set S and a map g : S → T ( S ) such that for any other coalgebra ( R , f : R → T ( R )) there is a unique map m : R → S使得T ( m ) ∘ f = g ∘ m 。
m由 map 递归地唯一定义,当f映射到Left ()
) 时返回Left ()
,当f映射到 Right (x, xs) 时返回Right (x, m xs)
Right (x, xs)
,即它是将余代数分配给其唯一态射到最终的余代数,并表示这种类型的独特变形/展开,这应该很容易说服自己实际上是一个可能为空且可能无限的 stream。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.