我刚读完论文Levity Polymorphism。
我有一个问题,为什么undefined
在用作未装箱的类型时可以是levity-polymorphic。
首先,让我们从论文的一些盒子定义开始:
盒装:
盒装值由指向堆的指针表示。
Int
和Bool
是包含盒装值的类型的示例。
装箱:
本文遵循一些轻浮的定义:
解除:
解除类型是一种懒惰的类型。
提升类型具有超出正常值的额外元素,表示非终止计算。
例如,类型Bool
已解除,这意味着Bool
有三个不同的值:True
,{{1} }和False
(底部)。
所有解除类型必须装箱。
未提升
未提升类型是严格的类型。
未提升类型中不存在元素⊥
。
⊥
和Int#
是未提升类型的示例。
本文接着解释了GHC 8如何提供允许类型变量具有多态性的功能。
这允许您编写具有以下类型的levity-polymorphic undefined
:
Char#
这表示undefined :: forall (r :: RuntimeRep). forall (a :: TYPE r). a
应该适用于任何 undefined
,甚至是未提升的类型。
以下是在GHCi中使用RuntimeRep
作为未装箱,未提升的undefined
的示例:
Int#
我一直认为> :set -XMagicHash
> import GHC.Prim
> import GHC.Exts
> I# (undefined :: Int#)
*** Exception: Prelude.undefined
与undefined
(底部)相同。然而,该论文称,“元素⊥
不存在于未提升的类型中。”
这里发生了什么?用作未提升类型时,⊥
实际上不是undefined
吗?我是否误解了论文(或盒子或轻浮)?
答案 0 :(得分:14)
这里的“Levity Polymorphism”论文的作者。
首先,我想澄清一些上述错误观念:
Bool
确实是一个提升的盒装类型。但是,它仍然只有2个值:True
和False
。表达式⊥根本不是一个值。它是一个表达式,变量可以绑定到⊥,但这不会使⊥成为一个值。或许更好的说法是,如果x :: Bool
,则评估x
可能会导致三种不同的结果:评估为True
,评估为False
,以及⊥ 。这里,⊥表示没有正常终止的任何计算,包括抛出异常(这是undefined
真正做的事情)并永远循环。
与最后一点类似,undefined
不是值。它是任何类型的居民,但它不是一个价值。在评估时,值无效 - 但undefined
不符合该规范。
根据您的观察方式,⊥可以以未提升的类型存在。例如,采用以下定义:
loop :: () -> Int#
loop x = loop x
此定义被GHC接受。然而,loop ()
是未提升的未装箱类型Int#
的⊥元素。在这方面,未提升类型和提升类型之间的区别在于无法将变量绑定到loop ()
。任何这样做的尝试(如let z = loop () in ...
)都会在变量被绑定之前循环。
请注意,这与非托管语言(如C)中的无限递归函数没有什么不同。
那么,我们如何允许undefined
拥有一个未提升的类型? @dfeuer说得对:undefined
秘密地是一个功能。它的完整类型签名是undefined :: forall (r :: RuntimeRep) (a :: TYPE r). HasCallStack => a
,这意味着它是一个函数,就像上面的loop
一样。在运行时,它将当前调用堆栈作为参数。因此,无论何时使用undefined
,您只需调用一个抛出异常的函数,不会造成任何麻烦。
事实上,在设计levity多态时,我们与undefined
进行了相当长的一段时间的斗争,各种各样的恶作剧使它成功。然后,当我们意识到undefined
具有HasCallStack
约束时,我们看到我们可以完全避开问题。老实说,如果没有HasCallStack
看似无关紧要的用户便利,我不知道我们会做些什么。