您正在检查的有趣属性是 undefined 对于我们选择的任何类型 a 具有类型 a,即没有约束的 undefined :: a。正如其他人所指出的,undefined 可能被认为是错误或无限循环。我想争辩说,最好将其视为“空洞的真实陈述”。在任何与停机问题密切相关的类型系统中,这几乎是一个不可避免的漏洞,但从逻辑的角度考虑它很有趣。
思考类型编程的一种方式是它是一个谜。有人给了你一个类型,并要求你实现一个该类型的函数。比如
Question: fn :: a -> a
Answer: fn = \x -> x
很简单。我们需要为任何类型的a 生成一个a,但是我们得到了一个作为输入,所以我们可以返回它。
有了undefined,这个游戏总是很简单
Question: fn :: Int -> m (f [a])
Answer: fn = \i -> undefined -- backdoor!
所以让我们摆脱它。当你生活在一个没有它的世界里时,理解undefined 是最容易的。现在我们的游戏变得更难了。有时是可能的
Question: fn :: (forall r. (a -> r) -> r) -> a
Answer: fn = \f -> f id
但突然之间,有时也不可能了!
Question: fn :: a -> b
Answer: fn = ??? -- this is `unsafeCoerce`, btw.
-- if `undefined` isn't fair game,
-- then `unsafeCoerce` isn't either
是吗?
-- The fixed-point combinator, the genesis of any recursive program
Question: fix :: (a -> a) -> a
Answer: fix = \f -> let a = f a in a
-- Why does this work?
-- One should be thinking of Russell's
-- Paradox right about now. This plays
-- the same role as a non-wellfounded set.
这是合法的,因为 Haskell 的 let 绑定是惰性的并且(通常)递归。现在我们是黄金。
Question: fn :: a -> b
Answer: fn = \a -> fix id -- This seems unfair?
即使没有内置undefined,我们也可以使用任何旧的无限循环在游戏中重建它。类型检查。为了真正防止我们在 Haskell 中使用 undefined,我们需要解决停机问题。
Question: undefined :: a
Answer: undefined = fix id
现在,正如您所见,undefined 对于调试很有用,因为它可以作为任何值的占位符。不幸的是,操作很糟糕,因为它要么导致无限循环,要么立即崩溃。这对我们的游戏也很不利,因为它让我们作弊。最后,我希望我已经证明,只要你的语言有(可能是无限的)循环,没有undefined 是相当困难的。
存在像 Agda 和 Coq 这样的语言,它们放弃循环以真正消除 undefined。他们这样做是因为我发明的这款游戏在某些情况下实际上非常有价值。它可以对逻辑语句进行编码,因此可以用来形成非常非常严格的数学证明。你的类型代表定理,你的程序保证了这个定理得到证实。 undefined 的存在意味着所有定理都是可证实的,从而使整个系统不可信。
但在 Haskell 中,我们对循环比校对更感兴趣,所以我们宁愿有 fix 也不愿确定没有 undefined。