有没有办法告诉Haskell中的列表是否是无限的?原因是我不想将函数(如length
)应用于无限列表。
将length
应用于未知列表通常是一个不好的想法,既因为无限列表的实际问题,也因为概念上通常情况下你实际上并不关心长度。
您在评论中说:
我对Haskell非常陌生,那么无限结构会不会让我的程序很容易出问题?
不会。虽然我们中的一些人希望有更好的方法来区分必须有限和必须无限的数据,但当您 创建、处理 和 逐步检查 惰性结构时,您总是安全的。计算长度显然不是逐步的,但检查长度是否高于或低于某个截止值是逐步的,而且经常这就是您想要做的全部工作!
一个微不足道的例子是测试非空列表。isNonEmpty xs == length xs > 0
是一个不好的实现,因为它检查了无限数量的元素,而检查单个元素就足够了!比较一下:
isNonEmpty [] = False
isNonEmpty (_:_) = True
null
的实现方式。drop
来做到这一点,而且不需要超过这个长度。longerThan :: Int -> [a] -> Bool
longerThan n xs = isNonEmpty $ drop n xs
给定一个长度为n
的、可能是无限长的列表xs
,如果存在,该函数删除xs
的前n
个元素,然后检查结果是否非空。因为如果n
大于列表长度,则drop
函数会产生空列表,所以对于所有正整数n
,此方法都可以正确地工作(遗憾的是,标准库中没有非负整数类型,例如自然数)。
停机问题最初是通过假设存在一个停机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
替换为isNonEmpty
即可。但显然可以实现isNonEmpty
。 - interjayisInfinite
适用于所有列表”,因为impossibleList
不是一个列表(它是⊥)。否则,你也可以说它同样表明了无法实现isNonEmpty
适用于所有列表,就像我在之前的评论中所说的那样。 - interjayisInfinite
,也没有涉及可决性或停机问题。考虑形式为f::Int32->Bool
的函数。由于可能的输入数量是有限的,它们都是可判定的。但是可以很容易地使用与上述几乎相同的构造来“证明”任何非平凡的f
不能为所有值计算出来。例如,对于 f x = x>0
,使用 impossibleValue = if (f impossibleValue) then 0 else 1
。 - interjayisInfinite
用于判断给定列表是否无限长,对于任何无限长的列表返回 True
,对于有限长的列表返回 False
... 如果你传递 ⊥,则不必在意。我们只能通过传递一个有限长的列表并显示它不会返回 False
(而是返回 True
或 ⊥),或者传递一个无限长的列表并显示它不会返回 True
(而是返回 False
或 ⊥)来得出矛盾。传递 ⊥ 并显示它生成 ⊥ 只能表明 isInfinite
是严格的。 - Tom Crockett我们不需要解决停机问题就可以安全地调用“length”函数。我们只需要保守一些,接受所有具有有限性证明的内容,拒绝所有没有(包括许多有限列表)。这正是类型系统的作用,因此我们使用以下类型(t 是我们忽略的元素类型):
terminatingLength :: (Finite a) => a t -> Int
terminatingLength = length . toList
Finite类只包含有限列表,因此类型检查器将确保我们有一个有限的参数。Finite的成员资格将是我们有限性的证明。 "toList"函数只是将Finite值转换为常规的Haskell列表:
class Finite a where
toList :: a t -> [t]
现在我们的实例是什么呢?我们知道空列表是有限的,因此我们创建一个数据类型来表示它们:
-- Type-level version of "[]"
data Nil a = Nil
instance Finite Nil where
toList Nil = []
-- 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函数的人必须证明他们的列表是有限的,否则他们的代码将无法编译。这并没有解决停机问题,但我们已经将其从运行时转移到了编译时。编译器可能会在尝试确定Finite成员身份时挂起,但这比在给定一些意外数据时让生产程序挂起要好。
请注意:Haskell的“特设”多态性允许在代码的其他点上声明几乎任意类型的Finite,并且terminatingLength将接受它们作为有限性证明,即使它们不是。不过这也没什么大不了的;如果有人试图绕过您代码的安全机制,他们会得到他们应得的错误信息;)
isInfinite x = length x `seq` False
isInfinite [1,2,3]
的结果为 False
,而 isInfinite [1..]
的结果也为 False
。 - alexkelboseq
确保计算长度,如果列表是无限的,它当然永远不会完成。因此,如果列表是有限的,则给出 False
(这是正确的),或者它从未完成,因此根本不会给出任何答案。而不存在的答案不能被称为“错误”,对吧?:] 是的,这是个笑话,如果这不明显的话。 - C. A. McCann