Haskell Data.Void: undefined 变成死循环

Haskell Data.Void: undefined turns into infinite loop

我注意到 haskell Data.Void 模块中 Void 类型的一件事,这很奇怪,我非常想知道为什么会这样. undefined 应该是每个类型都存在的值(在我的理解中),并且

undefined :: Type

应该产生

*** Exception: Prelude.undefined

它适用于我尝试过的所有数据类型,Void 除外。 undefined :: Void 根本不会终止,absurd undefined 也是如此。 现在我认为这不是什么大问题,因为 Void 代表一个无论如何都不会发生的值,但我仍然想知道是什么导致了这种行为。

经过更多的调查,我想我明白这是怎么发生的,以及它如何取决于 GHC 的优化选择。在void-0.7中,我们有如下相关定义:

newtype Void = Void Void

absurd :: Void -> a
absurd a = a `seq` spin a where
   spin (Void b) = spin b

instance Show Void where
  showsPrec _ = absurd

请注意 Show 实例委托给 absurd,这解释了为什么 GHCi 中的 undefined :: Void 给出与 absurd undefined.

相同的结果

现在查看 absurd a 的定义,它使用 seq 到 "ensure" 实际评估 a,因此您会认为应该触发 undefined 异常。然而,微妙的 seq 实际上并不是那样工作的。 a `seq` b 仅保证 both ab 将在整个表达式 returns 之前求值,但 不会按哪个顺序。这完全取决于实现的选择,首先评估哪一部分。

这意味着 GHC 可以自由地先评估 a,触发异常;否则首先评估 spin a 。如果它首先计算 spin a,那么你会得到一个无限循环,因为 Void 构造函数是一个 newtype 构造函数,并且根据设计,解包它们实际上不会计算任何东西。

因此,根据 seq 的语义,这两个选项实际上都是同样合法的 Haskell 行为。

作为最后的说明,exception semantics in GHC 已被明确定义为非确定性的:如果一个表达式可以以几种不同的方式给出 bottom,GHC 可以选择其中的任何一种。由于区分不同底部的唯一方法是IO,这被认为是可以接受的。