Haskell 中的列表是归纳的还是归纳的?
Posted
技术标签:
【中文标题】Haskell 中的列表是归纳的还是归纳的?【英文标题】:Are Lists Inductive or Coinductive in Haskell? 【发布时间】:2017-02-12 18:00:29 【问题描述】:所以我最近一直在阅读关于共归纳的文章,现在我想知道:Haskell 列表是归纳的还是共归纳的?我也听说 Haskell 不区分这两者,但如果是,他们是如何正式区分的?
列表以归纳方式定义,data [a] = [] | a : [a]
,但也可以以归纳方式使用,ones = a:ones
。我们可以创建无限列表。然而,我们可以创建有限列表。那么它们是什么?
Related 在 Idris 中,其中类型 List a
是严格的归纳类型,因此只是有限列表。它的定义类似于它在 Haskell 中的定义。但是,Stream a
是一种互感类型,对无限列表进行建模。它被定义为(或者更确切地说,定义等同于)codata Stream a = a :: (Stream a)
。创建无限列表或有限流是不可能的。但是,当我写定义时
codata HList : Type -> Type where
Nil : HList a
Cons : a -> HList a -> HList a
我从 Haskell 列表中得到了我期望的行为,即我可以创建有限和无限的结构。
所以让我把它们归结为几个核心问题:
Haskell 不区分归纳和互感类型吗?如果是这样,那它的形式化是什么?如果不是,那么哪个是[a]?
HList 是互感的吗?如果是这样,一个协约类型如何包含有限值?
如果我们定义 data HList' a = L (List a) | R (Stream a)
会怎样?与HList
相比,会考虑什么和/或它是否有用?
【问题讨论】:
Haskell 没有归纳或共归纳类型的概念。 (它实际上并不需要一个,因为它是一种部分且惰性的语言。)因此,询问给定的 Haskell 类型是归纳的还是协约的并没有真正的意义。 您的HList
类型通常称为共同列表。
【参考方案1】:
由于懒惰,Haskell 类型既是归纳的又是共归纳的,或者说,数据和余数据之间没有正式的区别。所有递归类型都可以包含无限嵌套的构造函数。在 Idris、Coq、Agda 等语言中,终止检查器会拒绝像 ones = 1 : ones
这样的定义。懒惰是指ones
可以一步计算为1 : ones
,而其他语言只能计算为范式,而ones
没有范式。
'Coinductive' 并不意味着'必然无限',它意味着'由它的解构方式定义',而 Inductive 意味着'由它的构造方式定义'。我认为this 是对细微差别的一个很好的解释。你肯定会同意类型
codata A : Type where MkA : A
不能无限。
这是一个有趣的 - 与 HList
相反,你永远无法“知道”它是有限的还是无限的(具体来说,如果列表是有限的,你可以在有限的时间内发现,但你可以'不计算它是无限的),HList'
为您提供了一种简单的方法来确定您的列表是有限的还是无限的。
【讨论】:
你能解释一下 Haskell 是如何同时拥有这两种类型的吗?我很确定这不是真的。 Haskell 中的每种类型都是惰性的。所以 Haskell 不能 表示像自然数这样的归纳类型。这根本不可能。 @gardenhead 在我的理解中,并不是所有的 Haskell 类型都“既是归纳的又是共归纳的”,而更多的是 Haskell 没有既没有归纳也没有共归纳类型。您也不能表示像Stream
这样的协导类型。在我看来,这更多是偏心而不是懒惰的结果。
@gardenhead 请记住,几乎所有将 Haskell 作为逻辑系统的正式处理都依赖于假设,例如 undefined
没有发挥作用,seq
也没有发挥作用。我说[]
既是归纳列表又是协归纳列表,因为您可以在 Haskell 列表上编写与例如Agda 或 Coq 在归纳和共归纳列表上运行。
@gardenhead 如果你接受上述假设,你真的可以用data Nat = Z | S !Nat
(注意严格性注释)来表示自然,其中inf = S inf
的定义是not生成的,更具体地说,它相当于undefined
。我想这同样适用于列表:data FiniteList = Nil | Cons a !(FiniteList a)
- 这又使ones
之类的东西无法生成,你得到有限的列表或底部。
@gardenhead 你是在谈论一种真正的编程语言还是一种理论语义,它具有判断函数何时因任意输入而停止的神奇能力?【参考方案2】:
在像 Coq 或 Agda 这样的总语言中,归纳类型是那些其值可以在有限时间内被拆除的类型。感应功能必须终止。另一方面,感应类型是那些值可以在有限时间内建立的类型。感应功能必须是高效的。
旨在用作证明助手的系统(如 Coq 和 Agda)必须是完整的,因为不终止会导致系统在逻辑上不一致。但是要求所有的函数都是全集的和归纳的,就不可能处理无限的结构,因此发明了共归纳法。
所以归纳和共归纳类型的目的是拒绝可能没有终止的程序。这是 Agda 中的一个示例,该示例由于生产力条件而被拒绝。 (您传递给filter
的函数可能会拒绝每个元素,因此您可能会永远等待结果流的下一个元素。)
filter : A : Set -> (A -> Bool) -> Stream A -> Stream A
filter f xs with f (head xs)
... | true = head xs :: filter f (tail xs)
... | false = filter f (tail xs) -- unguarded recursion
现在,Haskell 没有归纳或共归纳类型的概念。问题“这种类型是归纳的还是共归纳的?”不是一个有意义的。 Haskell 如何在不区分的情况下逃脱?好吧,Haskell 从一开始就从未打算将其作为一种逻辑保持一致。它是一种部分语言,这意味着您可以编写非终止和非生产性函数 - 没有终止检查器和生产力检查器。人们可以争论这一设计决策的智慧,但它肯定会使归纳和共归纳变得多余。
相反,Haskell 程序员习惯于非正式地推理程序的终止/生产力。懒惰让我们可以处理无限的数据结构,但我们没有从机器那里得到任何帮助来确保我们的功能是完整的。
【讨论】:
在Agda中,是否可以定义类似于HList的结构?在那种情况下,是否仍然施加生产力限制(因为它是 codata),因此过滤器仍然无效?也就是引入 Nil 不影响其有效性? 没错。归纳列表仍然可以是无限的(尽管与流不同,它们可能不是),所以filter
仍然会被拒绝,因为它不会对其所有输入都有效【参考方案3】:
要解释类型级递归,需要为 CPO 值列表函子
F X = (1 + A_bot * X)_bot
如果我们进行归纳推理,我们希望不动点是“最小的”。如果是协推式,则为“最大”。
从技术上讲,这是通过在 CPO_bot 的嵌入投影子类别中工作来完成的,例如对于嵌入图的“最小”限制
0_bot |-> F 0_bot |-> F (F 0_bot) |-> ...
推广 Kleene 不动点定理。对于“最大”,我们将采用投影图的限制
0_bot <-| F 0_bot <-| F (F 0_bot) <-| ...
然而事实证明,对于任何F
,“最小”与“最大”同构。这是“bilimit”定理(参见例如 Abramsky 的“Domain Theory”调查论文)。
也许令人惊讶的是,归纳或共归纳的味道来自F
应用的提升,而不是最小/最大固定点。例如,如果x
是粉碎的产品,#
是粉碎的总和,
F X = 1_bot # (A_bot x X)
将有限列表集作为双限制(直到 iso)。
[我希望我做对了——这些很棘手;-)]
【讨论】:
以上是关于Haskell 中的列表是归纳的还是归纳的?的主要内容,如果未能解决你的问题,请参考以下文章