我是 Haskell 的初学者,我被undefined
函数的类型签名吓了一跳。
我期待一些更简单的东西,但我在 Hackage 上找到了这个:
undefined :: forall (r :: RuntimeRep). forall (a :: TYPE r). HasCallStack => a
错误的特殊情况。预计编译器将认识到这一点并插入更适合
undefined
出现的上下文的错误消息。
你能解释一下这个签名是什么意思吗?
谢谢!
我是 Haskell 的初学者,我被undefined
函数的类型签名吓了一跳。
我期待一些更简单的东西,但我在 Hackage 上找到了这个:
undefined :: forall (r :: RuntimeRep). forall (a :: TYPE r). HasCallStack => a
错误的特殊情况。预计编译器将认识到这一点并插入更适合
undefined
出现的上下文的错误消息。
你能解释一下这个签名是什么意思吗?
谢谢!
对于初学者需要知道的所有方面,签名很简单
undefined :: a
a
这意味着,与通常量化的类型变量(即任何小写字母)一样,也可以明确表示:
{-# LANGUAGE ExplicitForall #-}
undefined :: forall a. a
...或者,因为我更喜欢写它
{-# LANGUAGE ExplicitForall, UnicodeSyntax #-}
undefined :: ∀ a. a
量化被推断为对所有类型,即所有具有种类的事物*
(阅读:“类型”,更准确地说是提升类型的种类——提升意味着它可以是懒惰的重击)。因此,您可以undefined
在任何表达式中使用,无论需要什么类型。
现在,undefined
当然是一个“部分函数”之类的东西,基本上是一个零参数的函数,它在任何地方都没有定义。(FTR,它不是函数,因为根据定义,函数具有参数[s]。)
您希望在实际评估它时获得有用的错误消息,但 GHC 默认情况下不会为所有内容生成调用堆栈(出于性能原因),因此过去的情况是错误消息几乎完全无用。这就是HasCallStack
出现的地方:这是一个约束,它基本上告诉某些代码可能会在其中发生的上下文undefined
,它应该注意它发生的位置,以便错误消息实际显示出来。所以,
undefined :: ∀ a. HasCallStack => a
HasCallStack
出现在 the 之后有点令人困惑∀ a
- 这实际上a
与将使用的上下文无关undefined
。只是,签名的形式总是
标识符::量词。约束=>类型
并且HasCallStack
是一个约束,这就是它出现在中间的原因。(更常见的是,约束实际上适用于您已量化的类型变量之一。)
最后,这个RunTimeRep
东西是关于levity polymorphism的。我自己也不是很理解,但是在为什么未定义的函数 levity-polymorphic 与未装箱类型一起使用时讨论了它?
这是@leftaroundabout 答案的延续。
在 Haskell 中,值具有类型。但是类型本身也有类型。当一种类型充当另一种类型的类型时,我们称其为“种类”。
Haskell 中最重要和最常见的类型是Type
,通常在签名中表示为*
. 它是被“提升”的类型,也就是说,其值可以是 thunk,在评估时可能会发散、抛出错误等......例如,类型Int
有 kind Type
。
还有其他类似的类型Int#
没有解除。类型的值Int#
永远不是thunk,它始终是内存中的实际值。
简而言之,值的内存表示由它们的类型控制。
RuntimeRep
是另一种。它是和 之类的LiftedRep
类型IntRep
。这些类型没有任何值,它们的存在只是为了在类型级别表达事物,正如我们将看到的。
有一个超神奇的类型级实体称为TYPE
它,当使用一种类型RuntimeRep
(即使用描述内存中表示的类型)参数化时,返回其值具有该表示的类型的种类。例如,Type
是TYPE LiftedRep
,而种类Int#
是TYPE IntRep
。
ghci> :set -XMagicHash
ghci> import GHC.Prim
ghci> import GHC.Types
ghci> import Data.Kind
ghci> :kind (Int :: TYPE 'LiftedRep)
(Int :: TYPE 'LiftedRep) :: *
ghci> :kind Int#
Int# :: TYPE 'IntRep
现在我们可以回到为什么undefined
会有这样一个奇怪的签名。问题是,我们希望能够undefined
在所有函数中使用,无论是返回 kind 类型的Type
函数,还是返回 kind 类型TYPE IntRep
(换句话说:Int#
类型)或返回另一个未提升类型的函数。否则,我们将需要多个不同版本的undefined
,这会很烦人。
解决方案是制作undefined
levity-polymorphic。签名说:对于任何可能的内存表示(RuntimeRep
)和任何可能的类型,其值具有该表示,undefined
计算该类型的成员。