Haskell 为什么绑定到一个";“无限制”;类型变量使变量刚性?

Haskell 为什么绑定到一个";“无限制”;类型变量使变量刚性?,haskell,Haskell,我有这个函数来生成一个随机值列表并返回生成器(如果有人知道更好的方法,请在评论中留下): 我想用它来洗牌列表。我创建了这个函数: shuffle gen stuff = (map snd $ sortOn fst $ zip rs stuff, gen') where rs :: [Int] (rs, gen') = randomN gen (length stuff) 我知道我需要显式地实例化rs,GHC不会神奇地计算出我想要如何对列表进行排序 问题是:我最初的尝试是这样的:

我有这个函数来生成一个随机值列表并返回生成器(如果有人知道更好的方法,请在评论中留下):

我想用它来洗牌列表。我创建了这个函数:

shuffle gen stuff = (map snd $ sortOn fst $ zip rs stuff, gen') where
    rs :: [Int]
    (rs, gen') = randomN gen (length stuff)
我知道我需要显式地实例化rs,GHC不会神奇地计算出我想要如何对列表进行排序

问题是:我最初的尝试是这样的:

shuffle gen stuff = (map snd $ sortOn fst $ zip rs stuff, gen') where
    (rs, gen') = randomN gen (length stuff) :: ([Int], g)
但该操作失败,出现以下错误:

shuffle_minimal.hs:11:18: error:
    • Couldn't match type ‘t1’ with ‘g’
        because type variable ‘g’ would escape its scope
      This (rigid, skolem) type variable is bound by
        an expression type signature:
          ([Int], g)
        at shuffle_minimal.hs:11:18-57
      Expected type: ([Int], g)
        Actual type: ([Int], t1)
    • In the expression: randomN gen (length stuff) :: ([Int], g)
      In a pattern binding:
        (rs, gen') = randomN gen (length stuff) :: ([Int], g)
      In an equation for ‘shuffle’:
          shuffle gen stuff
            = (map snd $ sortOn fst $ zip rs stuff, gen')
            where
                (rs, gen') = randomN gen (length stuff) :: ([Int], g)
    • Relevant bindings include
        gen :: t1 (bound at shuffle_minimal.hs:10:9)
        shuffle :: t1 -> [b] -> ([b], t) (bound at shuffle_minimal.hs:10:1)
如果类型变量像“g”一样通用,那么编译器为什么不能将其与任何其他类型匹配

什么是“因为类型变量‘g’将脱离其作用域”?我试过阅读,但并不真正理解:-(

谢谢

问题是:[……]

如果类型变量像“g”一样通用,那么编译器为什么不能将其与任何其他类型匹配

首先,请注意,为
shuffle
提供类型注释可以防止出现此错误消息。事实上,这就是为什么大多数Haskeller强烈建议为所有顶级绑定(至少)添加类型注释的原因

如果没有显式类型为GHC提供您的意图,GHC必须猜测——使用其推理引擎进行猜测

shuffle gen stuff = ...
什么类型是
gen
?GHC不知道这一点,因此它会生成一个新的类型变量
t1
,以表示其类型。此变量不是严格的,这意味着GHC以后可能会选择将
t1
与其他类型统一。例如,如果您的代码包含
gen==“hi”
然后GHC将统一
t1~字符串

后来我们发现

randomN gen (length stuff) :: ([Int], g)
这实际上意味着

randomN gen (length stuff) :: forall g. ([Int], g)
这意味着这种表达式的类型是
([Int],String)
([Int],Bool)
([Int],随便什么)
。对
g
的隐式通用量化表示“任意类型”。这里
g
是严格的:GHC不能只选择一种类型——签名要求所有类型

但是,表达式的类型是
([Int],t1)
,因为它的第二对返回与
gen
相同类型的新生成器。这将使
t1
g
统一,但这没有意义!事实上,
t1
代表单一类型,
g
代表“任意”类型类型:选择
t1~g
会隐式地将
g
固定为单个类型。或者换句话说,我们不能选择
t1
为“等于所有类型
g
”——这将是无稽之谈


GHC通过比较
g
t1
的范围来防止这种错误的统一:由于非刚性
t1
的范围更大,它不能与刚性
g

统一,因为它与
g
不同

在您的定义中推断出的
shuffle
类型如下所示:

 shuffle :: (RandomGen t1) => t1 -> [a] -> [a]
 shuffle gen stuff = ...
      where 
          (res, gen') = ... :: (Int, g)
 shuffle :: forall g a. (RandomGen g) => g -> [a] -> [a]
 shuffle gen stuff = ...
      where 
          (res, gen') = ... :: (Int, g)
从编译器的角度来看,
shuffle
type signature
t1
中的生成器类型和
gen'
signature
g
中的生成器类型是不同的类型。在您的代码中没有任何东西表明它们是相同的类型。因此编译器抱怨

第一个显而易见的解决方案是明确地给它们起相同的名字:

 shuffle :: (RandomGen g) => g -> [a] -> [a]
 shuffle gen stuff = ...
      where 
          (res, gen') = ... :: (Int, g)
然而,这也行不通。由于一些复杂的历史原因,即使这样声明,这两个
g
类型仍然不会被视为同一类型

是的,这是令人沮丧的。因此现代GHC提供了几个方便的扩展来修复它:并且(启用后者会自动启用前者)

启用这些扩展后,您可以修改函数,如下所示:

 shuffle :: (RandomGen t1) => t1 -> [a] -> [a]
 shuffle gen stuff = ...
      where 
          (res, gen') = ... :: (Int, g)
 shuffle :: forall g a. (RandomGen g) => g -> [a] -> [a]
 shuffle gen stuff = ...
      where 
          (res, gen') = ... :: (Int, g)

签名中的
forall
(由
ExplicitForAll
扩展名启用)重要的是:它为类型变量打开了一个新的作用域,并表示变量
g
a
是该作用域中的两个新类型。作用域大小是整个函数体,启用
ScopedTypeVariables
后,在该作用域中提及这些类型变量将意味着引用这些相同的类型,而不是n一个新类型。

我的印象是,如果没有启用
ScopedTypeVariables
,即使提供类型签名也不会有帮助,是吗?@FyodorSoikin更正。依我看,默认情况下,该扩展应该是打开的。然而,即使没有它,编写签名至少也会提供更好的错误消息。但是,即使使用扩展名启用了ion,您不需要为所有都使用显式的
吗?据我所知,类型签名不会自动创建一个新的类型作用域,是吗?我想我遗漏了一些东西。添加
ScopedTypeVariables
和类型签名shuffle::(RandomGen g)=>g->[a]>([a],g)仍然不起作用。不过谢谢你的解释!编辑:我现在读了@FyodorSoikin的回复,其中包括修复整个问题的显式forall。@FyodorSoikin是的,你需要它,否则扩展没有效果。另外,可能值得注意的是,
ScopedTypeVariables
启用
ExplicitForAll
自动嗯。