正如问题的标题所示,我想知道什么是Levity多态性,其动机是什么?我知道此页面中包含一些详细信息,但其中的大多数解释都超出了我的头脑。:)
尽管此页面友好一些,但我仍然无法理解其背后的动机。
Answers:
注意:此答案基于有关Levity讨论的最新观察结果。有关Levity多态性的所有内容目前仅在GHC 8.0版本候选版本中实现,因此可能会发生变化(例如,参见#11471)。
TL; DR:这是一种使函数在提升和未提升类型上多态的方法,而常规函数则无法实现。例如,下面的代码由于Int#
具有kind #
,所以没有使用常规多态进行类型检查,但是其中的类型变量id
具有kind *
:
{-# LANGUAGE MagicHash #-}
import GHC.Prim
example :: Int# -> Int#
example = id -- does not work, since id :: a -> a
Couldn't match kind ‘*’ with ‘#’
When matching types
a0 :: *
Int# :: #
Expected type: Int# -> Int#
Actual type: a0 -> a0
In the expression: id
注意,(->)
仍然使用一些魔术。
在我开始回答这个问题之前,让我们退后一步,转到最常用的功能之一($)
。
什么是($)
类型?嗯,根据Hackage和报告,
($) :: (a -> b) -> a -> b
但是,这还不是100%完成。这是一个方便的小谎言。问题在于多态类型(如a
和b
)具有种类*
。但是,(库)开发人员($)
不仅希望将其用于具有kind的类型*
,还希望将其用于kind的类型#
,例如
unwrapInt :: Int -> Int#
虽然Int
有善良*
(可以是底部),但Int#
有善良#
(根本不能是底部)。仍然,以下代码类型检查:
unwrapInt $ 42
那不行 还记得返回类型($)
吗?它是多态的,而多态类型却是善良的*
,不是#
!那为什么行得通呢?首先,它是一个bug,然后是一个黑客(ghc-dev邮件列表中Ryan Scott的邮件摘录):
那么为什么会这样呢?
长答案是,在GHC 8.0之前的类型签名中
($) :: (a -> b) -> a -> b
,b
实际上不是实物*
,而是实物OpenKind
。OpenKind
是一种可怕的骇客,它允许提升类型(kind*
)和未提升类型(kind#
)都居住,这就是类型检查的原因(unwrapInt $ 42)
。
那么($)
,GHC 8.0中的新类型是什么?它的
($) :: forall (w :: Levity) a (b :: TYPE w). (a -> b) -> a -> b
-- will likely change according to Richard E.
要了解它,我们必须查看Levity
:
data Levity = Lifted | Unlifted
现在,我们可以认为($)
具有以下两种类型之一,因为只有两种选择w
:
-- pseudo types
($) :: forall a (b :: TYPE Lifted). (a -> b) -> a -> b
($) :: forall a (b :: TYPE Unlifted). (a -> b) -> a -> b
TYPE
是一个神奇的常数,它重新定义了种*
和#
作为
type * = TYPE Lifted
type # = TYPE Unlifted
对种类的量化也相当新,并且是Haskell中依赖类型集成的一部分。
Levity多态性的名称来自于以下事实:您现在可以在提升类型和未提升类型上编写多态函数,而以前的多态性限制是不允许/可能的。同时也摆脱了OpenKind
黑客攻击。关于这两种处理,这确实是“正当的”。
顺便说一句,您并不孤单。甚至西蒙·佩顿·琼斯(Simon Peyton Jones)都说有必要创建一个Levity Wiki页面,而Richard E.(当前的实现者)表示,该Wiki页面需要对当前过程进行更新。
GHC.Types
,是ghc-prim
GHC随附的库的一部分。($) :: forall (w1 w2 :: Levity) (a :: TYPE w1) (b :: TYPE w2). (a -> b) -> a -> b
,即为什么($)
不应该将其与带有未修饰参数的函数一起使用?