我对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?
答案 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 x
和f
),但对于前者,您传入的函数必须适用于所有 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多态类型推断的额外复杂性是否值得讨论......