我正在尝试制作另一个Random实例,但由于类型错误而卡住了。我将其简化为以下ghci会话:
GHCi, version 8.6.5:
λ> import System.Random
λ> :t random
random :: (Random a, RandomGen g) => g -> (a, g)
λ> :t \g -> random g
\g -> random g :: (Random a, RandomGen g) => g -> (a, g)
λ> :t \g -> let xh = random g in xh
\g -> let xh = random g in xh
:: (Random a, RandomGen g) => g -> (a, g)
λ> :t \g -> let (x, h) = random g in (x, h)
<interactive>:1:11: error:
• Could not deduce (Random a0)
from the context: (Random a, RandomGen b)
bound by the inferred type of
it :: (Random a, RandomGen b) => b -> (a, b)
at <interactive>:1:1
The type variable ‘a0’ is ambiguous
• When checking that the inferred type
h :: forall a. Random a => b
is as general as its inferred signature
h :: b
In the expression: let (x, h) = random g in (x, h)
In the expression: \ g -> let (x, h) = ... in (x, h)
为什么在最新版本中失败?
答案 0 :(得分:2)
这是一个可爱的问题。您可能会想像这会转换为显式词典传递样式,例如:
-- original
foo = \g -> let (x, h) = random g in (x, h)
-- assumed but incorrect explicit version
foo = /\a. /\b. \\(da :: Random a) => \\(db :: RandomGen b) =>
\(g :: b) -> let tmp = random @a @b ,da ,db g
x = case tmp of (x, h) -> x
h = case tmp of (x, h) -> h
in (x, h)
在这里,我使用了一些自定义的新语法:
/\ty. tm
是显式类型抽象;它采用一种类型,将其命名为ty
,然后按术语tm
进行命名tm @ty
是显式类型的应用程序;它使用一个多态项tm
并提供ty
作为第一类参数\\(dict :: c) => tm
是显式的类型类抽象;它使用一个类型类字典作为约束c
的证据,将其命名为dict
,然后像术语tm
一样进行tm ,dict
是显式类型类应用程序;它使用一个假设某个类型类实例的术语,并提供字典dict
作为该实例的证据如果这是事物的编译方式,那么就不会有歧义,\g -> let (x, h) = random g in (x, h)
的类型将与之前的所有术语相同。但这不是模式绑定实际编译的方式,因为这使得x
和h
的类型比它们需要的类型更具限制性。而是以这种方式编译的:
-- actual explicit version
foo = /\a. /\b. /\c. \\(da :: Random a) => \\(db :: Random b) => \\(dc :: RandomGen c) =>
\(g :: b) -> let x = /\d. \\(dd :: Random d) =>
case random @d @c ,dd ,dc g of (x, h) -> x
h = /\d. \\(dd :: Random d) =>
case random @d @c ,dd ,dc g of (x, h) -> h
in (x @a ,da , h @b ,db)
这是 more polymorphic ,因为在计算Random
时用于选择x
实例的类型与用于选择{{1} }计算Random
时的实例。在这种情况下,这不是您想要的;但是有可能在其他情况下幻想这正是您想要的,并且将这两种类型捆绑在一起对于表达您想要的计算非常不便。
但是,由于此翻译所带来的额外灵活性,我们在这里遇到了一些束缚:事情的调用方如何影响为h
选择哪种类型(因此用于b
的字典,因为db
不会出现在整体类型的任何位置!因此,编译器会为您提供类型歧义错误。作为一种直观的解释:我们知道要使用哪个b
实例来计算Random
,因为调用者必须为元组的第一部分选择一个类型;但是我们不知道该使用哪个实例来计算x
,因为为h
选择一种类型并不能确定用于丢弃的h
的一半计算类型定义x
。
您可以要求编译器通过启用h
扩展名来选择第一个翻译。您可以详细了解here。有时它会阻止MonoLocalBinds
绑定以上述方式变为多态,这意味着类型推断将具有更多线索,但是某些其他可以接受的程序将不再进行类型检查。