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 signaturet1
中的生成器类型和gen'
signatureg
中的生成器类型是不同的类型。在您的代码中没有任何东西表明它们是相同的类型。因此编译器抱怨
第一个显而易见的解决方案是明确地给它们起相同的名字:
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
自动嗯。