我们不需要解决停止问题就可以安全地调用“长度”。我们只需要保守一点;接受所有有有限性证明的东西,拒绝所有没有的东西(包括许多有限列表)。这正是类型系统的用途,因此我们使用以下类型(t 是我们的元素类型,我们忽略它):
terminatingLength :: (Finite a) => a t -> Int
terminatingLength = length . toList
Finite 类将只包含有限列表,因此类型检查器将确保我们有一个有限参数。 Finite 的成员资格将是我们有限性的证明。 “toList”函数只是将有限值转换为常规 Haskell 列表:
class Finite a where
toList :: a t -> [t]
现在我们的实例是什么?我们知道空列表是有限的,所以我们创建了一个数据类型来表示它们:
-- Type-level version of "[]"
data Nil a = Nil
instance Finite Nil where
toList Nil = []
如果我们将一个元素“cons”到一个有限列表上,我们会得到一个有限列表(例如,如果“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 函数的人现在都必须证明他们的列表是有限的,否则他们的代码将无法编译。这并没有消除停机问题,但我们已将其转移到编译时而不是运行时。编译器在尝试确定 Finite 成员资格时可能会挂起,但这比在给定一些意外数据时让生产程序挂起要好。
请注意:Haskell 的“ad-hoc”多态性允许在代码的其他点声明几乎任意的 Finite 实例,并且 terminatingLength 将接受这些作为有限性证明,即使它们不是。不过,这还不错。如果有人试图绕过您代码的安全机制,他们会得到应得的错误;)