这有效:
data Wrapped a = Wrapped a
alpha :: IO s -> IO ()
alpha x = do
rv <- wrapit x
return ()
where
wrapit :: IO s -> IO (Wrapped s)
wrapit x' = do
a <- x'
return (Wrapped a)
这不是:
data Wrapped a = Wrapped a
alpha :: IO s -> IO ()
alpha x = do
rv <- wrapit
return ()
where
wrapit :: IO (Wrapped s)
wrapit = do
a <- x
return (Wrapped a)
为什么?
答案 0 :(得分:19)
这是由于在标准Haskell中对类型变量进行作用域和量化的方式。您可以使第二个版本像这样工作:
{-# LANGUAGE RankNTypes, ScopedTypeVariables #-}
module RigidProblem where
data Wrapped a = Wrapped a
alpha :: forall s. IO s -> IO ()
alpha x = do
rv <- wrapit
return ()
where
wrapit :: IO (Wrapped s)
wrapit = do
a <- x
return (Wrapped a)
有两处更改:启用了RankNTypes和ScopedTypeVariables语言扩展,并在forall s
的类型签名中添加了显式alpha
。这两个扩展中的第一个允许我们引入显式forall s
,从而将s
置于alpha
主体内的范围内,而第二个扩展使得wrapit
上的签名类型推断引擎不会使用{1}}来包含隐式forall s
- 而是使用该签名中的s
来命名一个类型变量,该变量应该在范围内并通过它
如果我理解正确的话,Haskell中的当前默认情况是所有刚性类型变量(意味着程序员明确提供的类型签名中出现的类型变量)是隐式量化的,并且不词法作用域,因此,没有办法在内部范围内提供的显式签名中引用外部作用域中的刚性类型变量...(哦,麻烦,我确定有人可以比这更好地说出它。)无论如何,从类型检查器的观点,s
签名中的alpha
和wrapit
签名中的t
完全无关,无法统一 - 因此错误。
有关详情,请参阅GHC文档中的this page和Haskell Prime wiki中的this page。
更新:我刚才意识到我从未解释为什么第一个版本有效。为了完整起见:请注意,对于第一个版本,您可以在s
的签名中使用wrapit
代替wrapit
,并且不会发生任何变化。您甚至可以从where
块中取出wrapit x
并使其成为单独的顶级函数。关键点在于它是一个多态函数,因此x
的类型由wrapit
的类型决定。第一个版本alpha
的签名中使用的类型变量与wrapit
签名中使用的类型变量的关系在这里没有任何用处。对于第二个版本,这当然是不同的,你必须诉诸上面提到的技巧,使s
的{{1}}与alpha
的{{1}}相同。
答案 1 :(得分:6)
MichałMarczyk上面的答案是正确的,但值得注意的是,如果删除wrapit函数的类型签名,第二个版本确实有效:
data Wrapped a = Wrapped a
alpha :: IO s -> IO ()
alpha x = do
rv <- wrapit
return ()
where
-- No type signature here!
wrapit = do
a <- x
return (Wrapped a)
也就是说,问题不在于代码本身;这就是Haskell 98不允许你为wrapit
函数写一个类型签名,因为它包含一个由其上下文绑定的类型变量(外部alpha
函数),而H98没有表达这一点的方式。正如Michał所说,启用ScopedTypeVariables
可以让你这样做。