这是如何构成Haskell刚性类型错误的?

时间:2010-02-18 06:42:12

标签: haskell types

这有效:

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)

为什么?

2 个答案:

答案 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签名中的alphawrapit签名中的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可以让你这样做。