如何判断列表是否是无限的?
有没有办法判断 Haskell 中的列表是否是无限的?原因是我不想将诸如 length
之类的函数应用于无限列表。
Is there a way to tell if a list in Haskell is infinite? The reason is that I don't want to apply functions such as length
to infinite lists.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
将
length
应用于未知列表通常是一个坏主意,实际上是由于无限列表,并且从概念上讲,因为通常事实证明您实际上并不关心长度。你在评论中说:
并不真地。虽然我们中的一些人希望有更好的方法来区分必然有限的数据和必然无限的数据,但当您创建、处理和检查< /em> 惰性结构增量。计算长度显然不是增量的,而是检查长度是否高于或低于某个截止值是,而且通常这就是您想要做的!
一个简单的例子是测试非空列表。
isNonEmpty xs == 长度xs > 0
是一种糟糕的实现,因为它检查无限数量的元素,而检查单个元素就足够了!比较一下:这不仅可以安全地应用于无限列表,而且在有限列表上也更加高效——它只需要常数时间,而不是与列表长度呈线性关系的时间。这也是标准的方式库函数
null
已实现。为了将其推广到相对于截止值的长度测试,您显然需要检查与要比较的长度一样多的列表。我们可以使用标准库函数
drop
来做到这一点,仅此而已:给定长度
n
和一个(可能是无限的)列表xs
,这将删除xs
的前n
元素(如果存在),然后检查结果是否非空。因为如果n
大于列表的长度,drop
会生成空列表,所以这对于所有正n
都能正确工作(唉,没有非负整数类型,例如标准库中的自然数)。这里的关键点是,在大多数情况下,最好将列表视为迭代流,而不是简单的数据结构。如果可能,您想要执行诸如转换、累积、截断等操作,并且生成另一个列表作为输出,或者仅检查列表的已知有限数量,而不是尝试一次性处理整个列表。
如果您使用这种方法,您的函数不仅可以在有限和无限列表上正常工作,而且它们还将从惰性和 GHC 优化器中受益更多,并且可能运行得更快并使用更少的内存。
Applying
length
to unknown lists is generally a bad idea, both practically due to infinite lists, and conceptually because often it turns out that you don't actually care about the length anyway.You said in a comment:
Not really. While some of us wish there were better ways to distinguish between necessarily finite and necessarily infinite data, you're always safe when you create, process, and examine lazy structures incrementally. Computing the length is clearly not incremental, but checking to see if the length is above or below some cut-off value is, and very often that's all you wanted to do anyway!
A trivial case is testing for nonempty lists.
isNonEmpty xs == length xs > 0
is a bad implementation because it examines an unbounded number of elements, when examining a single one would suffice! Compare this:Not only is this is safe to apply to an infinite list, it's also much more efficient on finite lists--it takes only constant time, instead of time linear in the length of the list. It's also how the standard library function
null
is implemented.To generalize this for length testing relative to a cut-off, you'll obviously need to examine as much of the list as the length you're comparing to. We can do exactly this, and no more, using the standard library function
drop
:Given a length
n
and a (possibly infinite) listxs
, this drops the firstn
elements ofxs
if they exist, then checks to see if the result is non-empty. Becausedrop
produces the empty list ifn
is larger than the length of the list, this works correctly for all positiven
(alas, there's no non-negative integer type, e.g. natural numbers, in the standard libraries).The key point here is that it's better in most cases to think of lists as iterative streams, not a simple data structure. When possible you want to do things like transform, accumulate, truncate, etc., and either produce another list as output or examine only a known-finite amount of the list, rather than trying to process the entire list in one go.
If you use this approach, not only will your functions work correctly on finite and infinite lists both, but they'll also benefit more from laziness and GHC's optimizer, and be likely to run faster and use less memory.
停止问题首先被证明是无法解决的,方法是假设存在停止预言机,然后编写一个函数来执行以下操作:与神谕所说的相反的事情将会发生。让我们在这里重现这一点:
现在,我们想要创建一个列表
impossibleList
,它的作用与isInfinite
所说的相反。因此,如果impossibleList
是无限的,那么它实际上是[]
,如果它不是无限的,那么它就是something : ImpossibleList
。在 ghci 中使用
isInfinite = const True
和isInfinite = const False
亲自尝试一下。The Halting Problem was first proved unsolvable by assuming a Halting Oracle existed, then writing a function that did the opposite of what that oracle said would happen. Let's reproduce that here:
Now, we want to make a list
impossibleList
that does the opposite of whatisInfinite
says it should. So, ifimpossibleList
is infinite, it is actually[]
, and if it isn't infinite, it issomething : impossibleList
.Try this out yourself in ghci with
isInfinite = const True
andisInfinite = const False
.我们不需要解决停止问题来安全地调用“length”。我们只需保持保守即可;接受所有有有限性证明的东西,拒绝所有没有有限性证明的东西(包括许多有限列表)。这正是类型系统的用途,因此我们使用以下类型(t 是我们的元素类型,我们忽略它):
Finite 类仅包含有限列表,因此类型检查器将确保我们有有限的参数。 Finite 的成员资格将成为我们有限性的证明。 “toList”函数只是将有限值转换为常规 Haskell 列表:
现在我们的实例是什么?我们知道空列表是有限的,因此我们创建一个数据类型来表示它们:
如果我们将一个元素“cons”到有限列表上,我们会得到一个有限列表(例如,如果“xs”是,“x:xs”是有限的)有限):
任何调用我们的 terminationLength 函数的人现在都必须证明他们的列表是有限的,否则他们的代码将无法编译。这并没有消除停止问题问题,但我们已将其转移到编译时而不是运行时。编译器在尝试确定 Finite 的成员资格时可能会挂起,但这比生产程序在收到一些意外数据时挂起要好。
需要注意的是:Haskell 的“临时”多态性允许在代码中的其他点声明几乎任意的 Finite 实例,并且 terminationLength 会接受这些作为有限性证明,即使它们不是。不过这还不算太糟糕;如果有人试图绕过您代码的安全机制,他们就会得到应有的错误;)
We don't need to solve the Halting Problem to call 'length' safely. We just need to be conservative; accept everything that has a finiteness proof, reject everything that doesn't (including many finite lists). This is exactly what type systems are for, so we use the following type (t is our element type, which we ignore):
The Finite class will only contains finite lists, so the type-checker will ensure we have a finite argument. membership of Finite will be our proof of finiteness. The "toList" function just turns Finite values into regular Haskell lists:
Now what are our instances? We know that empty lists are finite, so we make a datatype to represent them:
If we 'cons' an element on to a finite list, we get a finite list (eg. "x:xs" is finite if "xs" is finite):
Anyone calling our terminatingLength function must now prove that their list is finite, otherwise their code won't compile. This hasn't removed the Halting Problem issue, but we have shifted it to compile-time rather than run-time. The compiler may hang while trying to determine membership of Finite, but that's better than having a production program hang when it's given some unexpected data.
A word of caution: Haskell's 'ad-hoc' polymorphism allows pretty much arbitrary instances of Finite to be declared at other points in the code, and terminatingLength will accept these as finiteness proofs even if they're not. This isn't too bad though; if someone's trying to bypass the safety mechanisms of your code, they get the errors they deserve ;)
不——你最多只能估计一下。请参阅停止问题。
No - you may at best estimate. See the Halting Problem.
还可以通过设计分离有限列表和无限列表并为它们使用不同的类型。
不幸的是,Haskell(与例如 Agda 不同)不允许您强制执行数据结构始终是有限的,您可以采用全函数式编程技术来确保这一点。
您可以声明无限列表(又名流),因为
它没有任何方式如何终止序列(它基本上是一个没有
[]
的列表)。There is also the possibility of separating finite and infinite lists by design and use distinct types for them.
Unfortunately Haskell (unlike Agda for example) doesn't allow you to enforce that a data structure is always finite, you can employ techniques of total functional programming to ensure that.
And you can declare infinite lists (AKA streams) as
which doesn't have any way how to terminate a sequence (it's basically a list without
[]
).