我认为回答你的问题需要很多解释,所以这里有一个很长的答案,最后是对你问题的具体答案。
Data 和 codata 在范畴论方面有正式的数学定义,所以这不只是它们在程序中的使用方式(即,不仅仅是你在cmets)。在 Haskell 中可能看起来是这样,因为语言的特性(特别是非终止和惰性)最终会模糊区分,所以 在 Haskell 中,所有数据也是 codata,反之亦然,但它没有不一定要这样,而且有些语言可以更清楚地区分。
data 和 codata 确实在编程领域都有有用的表示,这些表示产生了与递归和核心递归的自然关系。
如果不快速掌握技术知识,很难解释这些正式的定义和表示,但粗略地说,一个数据类型,比如整数列表,是一个类型 L 和一个构造函数:
makeL :: Either () (Int, L) -> L
这在某种程度上是“普遍的”,因为它可以完全代表任何这样的结构。 (在这里,您想将 LHS 类型 Either () (Int, L) 解释为表示列表 L 是空列表 Left () 或由头部元素 h :: Int 和尾部列表 t :: L 组成的一对 Right (h, t) .)
举个反例,L = Bool 不是我们正在寻找的数据类型,因为即使你可以这样写:
foo :: Either () (Int, Bool) -> Bool
foo (Left ()) = False
foo (Right (h, t)) = True
要“构造”Bool,这不能完全代表任何这样的构造。例如,这两种结构:
foo (Right (1, foo (Left ()))) = True
foo (Right (2, foo (Left ()))) = True
给出相同的Bool 值,即使它们使用不同的整数,所以这个Bool 值不足以完全表示构造。
相比之下,[Int] 类型 是一种合适的数据类型,因为(几乎是微不足道的)构造函数:
makeL :: Either () (Int, [Int]) -> [Int]
makeL (Left ()) = []
makeL (Right (h, t)) = h : t
完全代表任何可能的构造,为每个构造创造独特的价值。因此,它在某种程度上是类型签名 Either () (Int, L) -> L 的“自然”构造。
类似地,整数列表的 codata 类型 将是一个类型 L 以及一个析构函数:
eatL :: L -> Either () (Int, L)
这在某种意义上是“普遍的”,因为它可以代表任何可能的破坏。
再次,从一个反例开始,一对(Int, Int) 不是我们正在寻找的余数据类型。例如,使用析构函数:
eatL :: (Int, Int) -> Either () (Int, (Int, Int))
eatL (a, b) = Right (a, (b, a))
我们可以代表破坏:
let p0 = (1, 2)
Right (1, p1) = eatL p0
Right (2, p2) = eatL p1
Right (1, p3) = eatL p2
Right (2, p4) = eatL p3
...continue indefinitely or stop whenever you want...
但我们不能代表破坏:
let p0 = (?, ?)
Right (1, p1) = eatL p0
Right (2, p2) = eatL p1
Right (3, p3) = eatL p2
Left () = eatL p3
另一方面,在 Haskell 中,列表类型 [Int] 是整数列表的合适余数据类型,因为析构函数:
eatL :: [Int] -> Either () (Int, [Int])
eatL (x:xs) = Right (x, xs)
eatL [] = Left ()
可以表示任何可能的破坏(包括有限或无限破坏,这要归功于 Haskell 的惰性列表)。
(作为证据表明这并不全是挥手,如果你想把它与形式数学联系起来,在技术范畴理论术语中,上面相当于说类似列表的 endofunctor:
F(A) = 1 + Int*A -- RHS equivalent to "Either () (Int,A)"
产生一个类别,其对象是构造函数(AKA F 代数)1 + Int*A -> A。与 F 关联的 data 类型是该类别中的初始 F 代数。 F 还产生了另一个类别,其对象是析构函数(AKA F-coalgebras)A -> 1 + Int*A。与 F 相关的 codata 类型是该类别中的最终 F-余代数。)
直观地说,正如@DanielWagner 所建议的那样,数据类型是表示类列表对象的任何构造的一种方式,而余数据类型是表示类列表对象的任何破坏的一种方式。在 data 和 codata 不同的语言中,有一个基本的不对称性——终止程序只能构造一个有限列表,但它可以破坏(第一部分)一个无限列表,因此 data 必须是有限的,但 codata 可以是有限的或无限。
这会导致另一个复杂情况。在 Haskell 中,我们可以使用makeL 来构造一个无限列表,如下所示:
myInfiniteList = let t = makeL (Right (1, t)) in t
请注意,如果 Haskell 不允许对非终止程序进行惰性求值,则这是不可能的。因为我们可以做到这一点,根据“数据”的正式定义,Haskell 整数列表数据类型还必须包含无限列表!也就是说,Haskell“数据”可以是无限的。
这可能与您可能在其他地方读到的内容相冲突(甚至与@DanielWagner 提供的直觉相冲突),其中“数据”仅用于指代有限的数据结构。好吧,因为 Haskell 有点奇怪,并且因为在数据和余数据不同的其他语言中不允许无限数据,所以当人们谈论“数据”和“余数据”(即使在 Haskell 中)并且有兴趣区分时,他们可能只使用“数据”来指代有限结构。
递归和核心递归适用于此的方式是,普遍性属性自然地给了我们“递归”来消费数据和“核心递归”来产生余数据。如果L 是具有构造函数的整数列表数据类型:
makeL :: Either () (Int, L) -> L
那么使用列表L 生成Result 的一种方法是定义一个(非递归)函数:
makeResult :: Either () (Int, Result) -> Result
这里,makeResult (Left ()) 给出了空列表的预期结果,而makeResult (Right (h, t_result)) 给出了头元素为 h :: Int 且尾部将给出结果 t_result :: Result 的列表的预期结果。
通过普遍性(即,makeL 是初始 F 代数的事实),存在一个独特的函数 process :: L -> Result,它“实现”makeResult。在实践中,它会递归实现:
process :: [Int] -> Result
process [] = makeResult (Left ())
process (h:t) = makeResult (Right (h, process t))
相反,如果L 是具有析构函数的整数列表余数据类型:
eatL :: L -> Either () (Int, L)
那么从Seed 生成列表L 的一种方法是定义一个(非递归)函数:
unfoldSeed :: Seed -> Either () (Int, Seed)
这里,unfoldSeed 应该为每个所需的整数生成 Right (x, nextSeed),并生成 Left () 以终止列表。
通过普遍性(即,eatL 是最终的 F-coalebra 的事实),存在一个独特的函数 generate :: Seed -> L,它“实现”unfoldSeed。在实践中,它将以核心递归方式实现:
generate :: Seed -> [Int]
generate s = case unfoldSeed s of
Left () -> []
Right (x, s') -> x : generate s'
综上所述,以下是您最初问题的答案:
-
从技术上讲,iterate f 是核心递归的,因为它是独特的生成代码数据的函数 Int -> [Int],它实现了:
unfoldSeed :: Seed -> Either () (Int, Seed)
unfoldSeed x = Right (x, f x)
通过上面定义的generate。
-
在 Haskell 中,产生 [a] 类型的 codata 的 corecursion 依赖于惰性。然而,严格的尾数据表示是可能的。例如,以下 codata 表示在 Strict Haskell 中运行良好,可以安全地进行全面评估。
data CoList = End | CoList Int (() -> CoList)
以下 corecursive 函数产生一个 CoList 值(我将其设为有限只是为了好玩——它也很容易产生无限余数据值):
countDown :: Int -> CoList
countDown n | n > 0 = CoList n (\() -> countDown (n-1))
| otherwise = End
所以,不,codata 不只是 WHNF 中具有 (a, thunk) 或类似形式的值的结果,并且 corecursion 不是值递归的同义词。但是,WHNF 和 thunk 提供了一种可能的实现,并且是“标准”Haskell 列表数据类型也是余数据类型的实现级别的原因。