使用未装箱类型时,为什么未定义的函数是levity-polymorphic?

时间:2018-04-23 17:01:31

标签: haskell polymorphism representation

我刚读完论文Levity Polymorphism

我有一个问题,为什么undefined在用作未装箱的类型时可以是levity-polymorphic。

首先,让我们从论文的一些盒子定义开始:

  • 盒装

    • 盒装值由指向堆的指针表示。

    • IntBool是包含盒装值的类型的示例。

  • 装箱

    • 未装箱的值由值本身表示(不是指向堆的指针)。

    • 来自Int#模块的
    • Char#GHC.Prim是具有未装箱值的类型的示例。

    • 未装箱的值不能是thunk。未装箱类型的函数参数必须按值传递。

本文遵循一些轻浮的定义:

  • 解除

    • 解除类型是一种懒惰的类型。

    • 提升类型具有超出正常值的额外元素,表示非终止计算。

    • 例如,类型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吗?我是否误解了论文(或盒子或轻浮)?

1 个答案:

答案 0 :(得分:14)

这里的“Levity Polymorphism”论文的作者。

首先,我想澄清一些上述错误观念:

  • Bool确实是一个提升的盒装类型。但是,它仍然只有2个值:TrueFalse。表达式⊥根本不是一个值。它是一个表达式,变量可以绑定到⊥,但这不会使⊥成为一个值。或许更好的说法是,如果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看似无关紧要的用户便利,我不知道我们会做些什么。