鉴于以下定义:
import Control.Monad.ST
import Data.STRef
fourty_two = do
x <- newSTRef (42::Int)
readSTRef x
以下在GHC下编译:
main = (print . runST) fourty_two -- (1)
但这不是:
main = (print . runST) $ fourty_two -- (2)
但是当 bdonlan 在评论中指出时,这会编译:
main = ((print . runST) $) fourty_two -- (3)
但是,这不会编译
main = (($) (print . runST)) fourty_two -- (4)
这似乎表明(3)仅由于中缀$
的特殊处理而编译,但是,它仍然没有解释为什么(1)编译。
问题:
1)我已经阅读了以下两个问题(first,second),并且我被引导相信$
只能用单态类型进行实例化。但我同样假设.
只能用单态类型实例化,结果同样会失败。
为什么第一个代码成功但第二个代码没有? (例如,对于第一种情况,GHC是否有特殊规则,它不适用于第二种情况?)
2)是否有编译第二个代码的当前GHC扩展? (也许ImpredicativePolymorphism在某些时候做到了这一点,但它似乎已被弃用,有什么东西取而代之吗?)
3)有没有办法定义说`my_dollar`
使用GHC扩展来执行$
所做的事情,但也能够处理多态类型,所以(print . runST) `my_dollar` fourty_two
编译?
编辑:建议答案:
此外,以下内容无法编译:
main = ((.) print runST) fourty_two -- (5)
除了不使用.
的中缀版本外,这与(1)相同。
因此,似乎GHC对$
和.
都有特殊规则,但只有中缀版本。
答案 0 :(得分:6)
我不确定我理解为什么第二个不起作用。我们可以看一下print . runST
的类型,并观察它是多态的,因此责任不在于(.)
。我怀疑GHC对中缀($)
的特殊规则是不够的。如果您将此片段作为其跟踪器上的错误提议,SPJ和朋友可能会重新检查它。
至于为什么第三个例子有效,那就是因为((print . runST) $)
的类型再次具有足够的多态性;实际上,它等于print . runST
。
ImpredicativePolymorphism
,因为GHC人员没有看到任何使用情况,额外的程序员方便性超过了编译器错误的额外可能性。 (我认为他们也不认为这很引人注目,但我当然不是权威。)我们可以定义稍微更少的多态($$)
:
{-# LANGUAGE RankNTypes #-}
infixl 0 $$
($$) :: ((forall s. f s a) -> b) -> ((forall s. f s a) -> b)
f $$ x = f x
然后您的示例类型检查可以使用此新运算符:
*Main> (print . runST) $$ fourty_two
42
答案 1 :(得分:0)
我不能在这个问题上说太多权威,但这是我认为可能发生的事情:
考虑一下typechecker在每种情况下必须做的事情。 (print . runST)
的类型为Show b => (forall s. ST s t) -> IO ()
。 fourty_two
的类型为ST x Int
。
forall
这里是一个存在类型限定符 - 这意味着传入的参数必须是s
上的通用。也就是说,您必须传入支持s
的任何值的多态类型。如果未明确声明forall
,则Haskell将其置于类型定义的最外层。这意味着fourty_two :: forall x. ST x Int
和(print . runST) :: forall t. Show t => (forall s. ST s t) -> IO ()
现在,我们可以通过forall x. ST x Int
将forall s. ST s t
与t = Int, x = s
匹配。所以直接调用案例有效。但是,如果我们使用$
会发生什么?
$
的类型为($) :: forall a b. (a -> b) -> a -> b
。当我们解析a
和b
时,由于$
的类型没有这样的任何显式类型范围,x
的{{1}}参数会被解除超出fourty_two
类型的最外层范围 - 所以($)
。此时,它会尝试匹配($) :: forall x t. (a = forall s. ST s t -> b = IO ()) -> (a = ST x t) -> IO ()
和a
,然后失败。
如果您改为编写b
,则编译器首先解析((print . runST) $) fourty_two
的类型。它将($)的类型解析为((print . runST $)
;请注意,由于forall t. (a = forall s. ST s t -> b = IO ()) -> a -> b
的第二次出现不受约束,我们没有那个讨厌的类型变量泄漏到最外层范围!所以匹配成功,部分应用了函数,表达式的整体类型为a
,它正好在我们开始的地方,所以它成功了。