为什么不使用forall(RankNTypes用法)?

时间:2012-04-12 17:58:01

标签: haskell ghc

我对forall不太熟悉,但最近读过这个问题:What does the `forall` keyword in Haskell/GHC do?

其中一个答案就是这个例子:

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

解释很好,我理解forall在这里做了什么。但我想知道,这是不是默认行为的特殊原因。有没有时间会有不利之处?

编辑:我的意思是,是否有理由不能默认插入forall?

2 个答案:

答案 0 :(得分:16)

嗯,它不是Haskell 2010标准的一部分,因此它默认情况下不会启用,而是作为语言扩展提供。至于为什么它不在标准中,rank-n类型比标准Haskell具有的普通rank-1类型更难实现;它们也经常不需要它们,所以委员会可能决定不为语言和实现简单而烦恼。

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

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

隐式a -> IO a明确表示可能更有意义:

forall

答案 1 :(得分:7)

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

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

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