8

我不是很熟悉forall,但最近读到这个问题:Haskell/GHC 中的`forall` 关键字是做什么的?

答案之一是这个例子:

 {-# LANGUAGE RankNTypes #-}
 liftTup :: (forall x. x -> f x) -> (a, b) -> (f a, f b)
 liftTup liftFunc (t, v) = (liftFunc t, liftFunc v)

解释很好,我明白forall这里在做什么。但我想知道,这不是默认行为是否有特殊原因。是否有过不利的时候?

编辑:我的意思是,默认情况下不能插入forall是有原因的吗?

4

2 回答 2

16

嗯,它不是 Haskell 2010 标准的一部分,所以默认情况下它不是打开的,而是作为语言扩展提供的。至于为什么它不在标准中,rank-n 类型比标准 Haskell 的普通 rank-1 类型更难实现;它们也不是那么频繁地需要,因此出于语言和实现简单的原因,委员会可能决定不打扰它们。

当然,这并不意味着 rank-n 类型没有用;它们确实如此,没有它们,我们就没有像STmonad这样有价值的工具(它提供了高效的本地可变状态——就像IO你所能做的就是使用IORefs)。但它们确实给语言增加了相当多的复杂性,并且在应用看似良性的代码转换时可能会导致奇怪的行为。例如,一些 rank-n 类型检查器将允许runST (do { ... })但拒绝runST $ do { ... },即使这两个表达式在没有 rank-n 类型的情况下总是等价的。有关它可能导致的意外(有时是令人讨厌的)行为的示例,请参阅此 SO 问题。

如果像 sepp2k 所问的那样,您是在问为什么forall必须明确地将其添加到类型签名中以获得更高的通用性,那么问题在于它(forall x. x -> f x) -> (a, b) -> (f a, f b)实际上是一个比(x -> f x) -> (a, b) -> (f a, f b). 使用后者,您可以传入任何形式的函数x -> f x(对于任何fand x),但对于前者,您传入的函数必须适用于all x。因此,例如,类型函数String -> IO String将是第二个函数的允许参数,但不是第一个函数;它必须有类型a -> IO a。如果后者自动转换为前者,那将是相当混乱的!他们是两种截然不同的类型。

forall将隐含的 s 明确化可能更有意义:

forall f x a b. (x -> f x)           -> (a, b) -> (f a, f b)
forall f a b.   (forall x. x -> f x) -> (a, b) -> (f a, f b)
于 2012-04-12T18:00:04.623 回答
7

我怀疑默认情况下不启用更高级别的类型,因为它们使类型推断 undecidable。这也是为什么,即使启用了扩展,您也需要使用forall关键字来获得更高等级的类型 - GHC 假定所有类型都是 rank-1,除非明确告知,以便推断尽可能多的类型信息。

换句话说,没有通用的方法来推断更高级别的类型(forall x. x -> f x) -> (a,b) -> (f a, f b),因此获得该类型的唯一方法是通过显式类型签名。

编辑:根据上面 Vitus 的评论,rank-2 类型推断是可确定的,但更高级别的多态性不是。所以这种类型签名在技术上是可推断的(尽管算法更复杂)。启用 rank-2 多态类型推断的额外复杂性是否值得值得商榷......

于 2012-04-12T20:03:09.553 回答