您正在检查的有趣属性是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
内置,我们也可以使用任何旧的无限循环在我们的游戏中重建它。类型检查。为了真正防止自己undefined
进入 Haskell,我们需要解决停机问题。
Question: undefined :: a
Answer: undefined = fix id
现在,正如您所见,undefined
它对调试很有用,因为它可以是任何值的占位符。不幸的是,操作很糟糕,因为它要么导致无限循环,要么立即崩溃。这对我们的游戏也很不利,因为它让我们作弊。最后,我希望我已经证明,undefined
只要你的语言有(可能是无限的)循环,就很难做到。
存在像 Agda 和 Coq 这样的语言,它们会交换循环以真正消除undefined
. 他们这样做是因为我发明的这款游戏在某些情况下实际上非常有价值。它可以对逻辑语句进行编码,因此可以用来形成非常非常严格的数学证明。你的类型代表定理,你的程序保证了这个定理得到证实。的存在undefined
意味着所有定理都是可证实的,从而使整个系统不可信。
但是在 Haskell 中,我们对循环比校对更感兴趣,所以我们宁愿拥有也fix
不愿确定没有undefined
.