如何判断一个列表是无限的?
有没有办法告诉如果Haskell中的列表是无限的? 原因是我不想将length
等函数应用到无限列表中。
对未知列表应用length
通常是一个糟糕的主意,实际上是由于无限列表,从概念上讲,因为通常事实certificate,你实际上并不关心这个长度。
你在评论中说:
我对Haskell很新,所以现在,不是无限的结构使我的程序非常脆弱?
不是真的。 虽然我们有些人希望有更好的方法来区分必然的有限的和无限的数据,但是当您逐渐 创build , 处理和检查惰性结构时,您总是安全的。 计算长度显然不是递增的,但要检查长度是高于还是低于某个临界值,并且通常这就是您想要的任何事情!
一个微不足道的情况是testing非空列表。 isNonEmpty xs == length xs > 0
是一个糟糕的实现,因为它检查一个无限数量的元素,当检查一个单独的元素就足够了! 比较一下:
isNonEmpty [] = False isNonEmpty (_:_) = True
这不仅适用于无限列表是安全的,而且在有限列表上也更加高效 – 它只需要一个固定时间,而不是列表长度的时间线性。 这也是标准库函数null
如何实现的 。
为了将这个概念推广到相对于截断的长度testing,您显然需要检查与您比较的长度一样多的列表。 我们可以做到这一点,而不再使用标准库函数drop
:
longerThan :: Int -> [a] -> Bool longerThan n xs = isNonEmpty $ drop n xs
给定一个长度为n
和一个(可能是无限的)列表xs
,如果它们存在,则会丢弃xs
的前n
元素,然后检查结果是否为非空。 因为如果n
大于列表的长度, drop
会产生空列表,这对于所有的正n
都是正确的(唉,标准库中没有非负整数types,例如自然数)。
关键在于,在大多数情况下,将列表视为迭代stream而不是简单的数据结构会更好。 如果可能的话,你想做的事情,如变换,累加,截断等,或者产生另一个列表作为输出或检查只有一个已知的有限数量的列表,而不是试图一次处理整个列表。
如果你使用这种方法,你的函数不仅可以在有限的和无限的列表上正确地工作,而且还可以从懒惰和GHC的优化器中获得更多的好处,并且可能运行得更快并且使用更less的内存。
暂停的问题首先被certificate是不能通过假定Oracle已经停止的,然后写出一个与Oracle说的事情相反的函数。 让我们在这里重现:
isInfinite :: [a] -> Bool isInfinite ls = {- Magic! -}
现在,我们要制作一个impossibleList
的清单,它和isInfinite
所说的应该是相反的。 所以,如果impossibleList
是无限的,那么它实际上是[]
,如果它不是无限的,它就是something : impossibleList
。
-- using a string here so you can watch it explode in ghci impossibleList :: [String] impossibleList = case isInfinite impossibleList of True -> [] False -> "loop!" : impossibleList
在ghci中用isInfinite = const True
和isInfinite = const False
尝试一下。
isInfinite x = length x `seq` False
我们不需要解决暂停问题来安全地调用“长度”。 我们只需要保守; 接受一切有限certificate的东西,拒绝一切不包含的东西(包括许多有限的列表)。 这正是系统的types,所以我们使用下面的types(t是我们的元素types,我们忽略):
terminatingLength :: (Finite a) => at -> Int terminatingLength = length . toList
有限类只包含有限的列表,所以types检查器将确保我们有一个有限的参数。 有限的成员将是我们有限性的certificate。 “toList”函数只是将有限值转换为常规的Haskell列表:
class Finite a where toList :: at -> [t]
现在我们的例子是什么? 我们知道空列表是有限的,所以我们build立一个数据types来表示它们:
-- Type-level version of "[]" data Nil a = Nil instance Finite Nil where toList Nil = []
如果我们把一个元素放到一个有限列表中,我们得到一个有限的列表(例如,如果xs是有限的,那么“x:xs”是有限的):
-- Type-level version of ":" data Cons va = Cons a (va) -- A finite tail implies a finite Cons instance (Finite a) => Finite (Cons a) where toList (Cons ht) = h : toList t -- Simple tail recursion
任何人调用我们的terminatingLength函数现在必须certificate他们的列表是有限的,否则他们的代码将不会编译。 这并没有消除暂停问题,但我们已经将其转移到编译时而不是运行时。 编译器在尝试确定Finite的成员资格时可能会挂起,但这比在给出一些意外的数据时生产程序挂起要好。
值得警惕的是:Haskell的“ad-hoc”多态允许在代码中的其他位置声明几乎任意的Finite实例,并且terminatingLength将接受这些实例作为有限certificate,即使它们不是。 不过这并不算太坏, 如果有人试图绕过你的代码的安全机制,他们会得到他们应得的错误;)
不,你可以估计。 看到停机问题 。
也有可能通过devise分离有限和无限的列表,并为它们使用不同的types。
不幸的是,Haskell(例如与Agda不同)不允许您强制执行一个数据结构总是有限的,您可以使用全function编程技术来确保。
你可以声明无限列表(AKAstream)
data Stream a = Stream a (Stream a)
它没有任何方式来终止序列(它基本上是一个没有[]
的列表)。