有没有办法判断Haskell中的列表是否无限?原因是我不想将诸如length
之类的函数应用于无限列表。
答案 0 :(得分:29)
将length
应用于未知列表通常是一个坏主意,实际上是由于无限列表,而且在概念上是因为通常情况下你实际上并不关心长度。
你在评论中说:
我对Haskell很新,所以现在,不是无限结构使我的程序非常脆弱吗?
不是真的。虽然我们中的一些人希望有更好的方法来区分必然有限和必然无限的数据,但当您创建,进程和检查<时,您总是安全的/ em>懒惰结构渐进式。计算长度显然不是递增的,但检查长度是否高于或低于某个截止值 ,并且通常这就是你想做的所有事情!
一个微不足道的案例是测试非空列表。 isNonEmpty xs == length xs > 0
是一个糟糕的实现,因为它检查了无限数量的元素,当检查一个元素就足够了!比较一下:
isNonEmpty [] = False
isNonEmpty (_:_) = True
这不仅适用于无限列表是安全的,它在有限列表上也更有效 - 它只需要恒定的时间,而不是列表长度的时间线性。它也是how the standard library function null
is implemented。
为了对相对于截止值进行长度测试进行概括,你显然需要检查列表的大小和你要比较的长度。我们可以使用标准库函数drop
:
longerThan :: Int -> [a] -> Bool
longerThan n xs = isNonEmpty $ drop n xs
给定长度n
和(可能是无限的)列表xs
,如果它们存在,则会删除n
的第一个xs
元素,然后检查是否存在结果是非空的。因为如果drop
大于列表的长度,n
会生成空列表,这对所有正n
都能正常工作(唉,没有非负整数类型,例如自然数,在标准库中。
这里的关键点是,在大多数情况下,将列表视为迭代流而不是简单的数据结构会更好。如果可能,您希望执行转换,累积,截断等操作,并生成另一个列表作为输出或仅检查已知有限量的列表,而不是尝试一次处理整个列表。
如果你使用这种方法,你的函数不仅可以在有限的和无限列表上正常工作,而且它们还可以从懒惰和GHC的优化器中获益更多,并且可能更快地运行使用更少的记忆。
答案 1 :(得分:26)
首先证明Halting Problem是无法解决的,假设存在Halting 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
使用isInfinite = const True
和isInfinite = const False
在
答案 2 :(得分:12)
isInfinite x = length x `seq` False
答案 3 :(得分:12)
我们不需要解决停机问题来安全地调用'长度'。我们只需要保守;接受所有具有有限性证明的东西,拒绝所有没有的东西(包括许多有限列表)。这正是类型系统的用途,因此我们使用以下类型(t是我们忽略的元素类型):
terminatingLength :: (Finite a) => a t -> Int
terminatingLength = length . toList
有限类只包含有限列表,因此类型检查器将确保我们有一个有限的参数。有限的成员资格将是我们有限的证明。 “toList”函数只是将有限值转换为常规Haskell列表:
class Finite a where
toList :: a t -> [t]
现在我们的实例是什么?我们知道空列表是有限的,所以我们创建一个数据类型来表示它们:
-- Type-level version of "[]"
data Nil a = Nil
instance Finite Nil where
toList Nil = []
如果我们'将'一个元素放到有限列表中,我们得到一个有限列表(例如,如果“xs”是有限的,则“x:xs”是有限的):
-- Type-level version of ":"
data Cons v a = Cons a (v a)
-- A finite tail implies a finite Cons
instance (Finite a) => Finite (Cons a) where
toList (Cons h t) = h : toList t -- Simple tail recursion
任何调用我们的terminatingLength函数的人现在必须证明他们的列表是有限的,否则他们的代码将无法编译。这并没有消除停机问题,但我们已将其转移到编译时而不是运行时。编译器可能在尝试确定有限元的成员资格时挂起,但这比生成程序在给出一些意外数据时挂起更好。
提醒一句:Haskell的'ad-hoc'多态性允许在代码中的其他点声明几乎任意的有限实例,而终止长度将接受这些作为有限证明,即使它们不是。这不是太糟糕了;如果有人试图绕过代码的安全机制,他们会得到他们应得的错误;)
答案 4 :(得分:5)
不 - 你可能充其量估计。请参阅Halting Problem。
答案 5 :(得分:1)
还有可能通过设计分离有限列表和无限列表,并为它们使用不同的类型。
不幸的是,Haskell(例如,与Agda不同)并不允许您强制执行数据结构总是有限的,您可以使用total functional programming的技术来确保这一点。
您可以将无限列表(AKA流)声明为
data Stream a = Stream a (Stream a)
没有任何方法可以终止序列(它基本上是一个没有[]
的列表。)