我想了一堆。其他人也是如此:
现在我自己的猜测。
在上述讨论中,大多数情况下的基本思想是,您可以!
在任何类型上加上 a 以获得该类型的严格、明确评估的 WHNF 版本。所以Int
可能是一个重击,而!Int
绝对不是一个。这为类型检查器提出了有趣的问题。!Int ~ Int
持有吗?如果不是——这两者是完全独立的、不兼容的类型——那么使用它们将是非常痛苦的。另一方面,如果它确实成立,则没有什么可以阻止在预期 an 的Int
地方传递未评估的!Int
- 毕竟,它们是相同的类型。理想情况下,您想要的是能够提供一个!Int
where 和Int
是预期的,但反之则不然。这听起来像一个子类型关系:!a <: a
,!a
是 的子类型a
,a
被评估和未评估的值(thunk)占据,而!a
只有被评估的值。GHC 的类型系统没有子类型,可能也没有,因为类型系统的其他部分不能很好地与之交互。这是一个高度受限的特定子类型实例:程序员不能声明任意子类型关系,而是存在一个单一的、硬编码的关系:forall a. !a <: a
. 我不知道这是否使它更合理地实施。
假设可以做到。你会得到更多的问题。如果您确实尝试提供预期的Int
位置怎么办?!Int
类型错误?理想情况下,我认为不会,而是编译器会插入代码来评估它,然后继续。好的。如何在预期的[Int]
地方提供,或者在一般情况下?编译器怎么可能知道如何遍历任何给定的结构来找到它包含的那些点,评估那些,并且只评估那些?那会是类型错误吗?让我们说它是。程序员如何手动进行转换——从? 也许通过映射一个函数?但这是荒谬的。Haskell 非常懒惰。如果你把它应用到一个论点,[!Int]
f a
f !a
a
f !a
f a
eval :: a -> !a
eval x
,然后在需要它的值之前,它将是一个 thunk。所以eval x
不可能有 type !a
。返回类型位置中的严格性注释没有任何意义。那么: data Wrap a = Wrap { unwrap :: a }
, eval' :: a -> Wrap !a
, 语义Wrap !a
可能是一个 thunk,但是编译器会插入代码,这样当你评估它时,!a
内部肯定也会被评估?实际上,这里有一个更简单的表述:( data Wrap' a = Wrap' { unwrap' :: !a }
) eval' = Wrap'
。这是现有的合法 Haskell,被我们新的严格类型所包含。这很好,我猜。但是一旦你尝试使用它,你就会再次遇到问题。你将如何从f a
到f !a
,再一次 - fmap unwrap . fmap Wrap
?但unwrap
有完全相同的问题eval
. 所以这一切似乎都不是那么微不足道。那么看似无害的反向案例呢:f !a
在f a
预期的地方供应?那样有用吗?(换句话说,f !a <: f a
?)这取决于如何a
在f
. 编译器必须了解协变、逆变和不变类型参数的位置——子类型化带来的另一件事。
这是我想透的。这似乎比看起来更难。
还有一件有趣的事。您可能听说过也可能没有听说过未提升类型的概念:没有底部居住的类型。这就是,据我所知,与此相同。保证被评估为 WHNF 的类型;保证不是底部的类型。没有区别,对吧?GHC 实际上已经有一堆未提升的类型作为基元(Int#
等),但它们是连线的(你不能添加新的)并且除了未提升之外还没有装箱,所以它们有不同的类型(#
而不是*
) 并且不能与普通类型混合。而!a
将是一种未提升但盒装的类型*
. 未提升的类型是我在类型理论上下文中多次提到的东西,所以也许有一些研究以更一般的方式实现它们。我还没看呢