【问题标题】:ambiguity error with `reads` in ghc-7.8ghc-7.8 中“读取”的歧义错误
【发布时间】:2014-04-24 22:22:43
【问题描述】:

我正在使用 GHC-7.8.2 测试 Write yourself a Scheme in 48 hours 的代码,这给了我一个关于歧义的错误,我不记得在以前的 GHC 版本中遇到过。 摘录如下,标出问题行:

data LispVal = Atom String
             | List [LispVal]
             | DottedList [LispVal] LispVal
             | Number Integer
             | String String
             | Bool Bool
unpackNum :: LispVal -> Integer
unpackNum (Number n) = n
unpackNum (String n) = let parsed = reads n in  --problem line 
                          if null parsed 
                            then 0
                            else fst $ parsed !! 0
unpackNum (List [n]) = unpackNum n
unpackNum _ = 0

,错误提示:

No instance for (Read a0) arising from a use of ¡®parsed¡¯
The type variable ¡®a0¡¯ is ambiguous
Note: there are several potential instances:
  instance Read a => Read (Control.Applicative.ZipList a)
    -- Defined in ¡®Control.Applicative¡¯
  instance Read () -- Defined in ¡®GHC.Read¡¯
  instance (Read a, Read b) => Read (a, b) -- Defined in ¡®GHC.Read¡¯
  ...plus 26 others

如果我将问题行更改为

unpackNum (String n) = let parsed = reads n ::[(Integer,String)] in 

然后一切正常。

我不明白为什么 GHC 无法从 unpackNum 的签名中推断出 ReadS 的类型。有人能解释一下是什么触发了错误吗?

(

-- 编辑--

只是一些跟进。据我了解,函数类型unpackNum :: LispVal -> Integer 以及fst $ parsed !! 0 是它的返回值这一事实表明parsed 的类型为[(Integer,b)],而从type ReadS a = String -> [(a,String)] 来看,parsed 应该是[(a, String)] .难道这两种类型不应该统一为[(Integer, String)],固定为parsed的类型吗?

有人能解释一下为什么NoMonomorphismRestriction 会打破上述推理吗?

-- 编辑2--

从答案中,我可以理解NoMonomorphismRestriction 是如何导致这里出现问题的。不过,我不明白的是,这种“同一表达式的两种类型”行为如何与 Haskell 中的懒惰相一致。在示例中,parsedreads n 在一个块中是相同的表达式,并且应该只计算一次。怎么可能第一次评估有a,第二次有Integer

)

谢谢,

【问题讨论】:

  • 代码只是用 ghc-7.8.2 为我编译 ...
  • @kosmikus:也在 ghc-7.8.2 上,我看到与 OP 相同的错误。

标签: haskell ghc


【解决方案1】:

如果NoMonomorphismRestriction 处于活动状态,则会触发此事件;顺便说一句,自 7.8 (see release notes, Section 1.5.2.3) 以来,GHCi 现在默认情况下就是这种情况。

如果禁用单态限制,则parsed的定义得到一个多态类型,即

parsed :: Read a => [(a, String)]

然后null parsed 中的第一次使用没有足够的上下文信息来解析a 是什么。

这恰好是单态限制确实有好处的少数情况之一。因为使用多态类型,即使两个使用站点都有足够的类型 信息来解决类约束,实际的解析会发生两次。

最好的解决方案仍然是按照 acomar 的回答中的建议使用模式匹配。

【讨论】:

  • 非常感谢您的回答。您能否详细说明为什么“null parsed 没有足够的上下文信息”?请在问题中查看我的更新。
  • 我们有null :: [b] -> Bool,所以null parsed :: Read a => Bool,也就是说,一旦我们选择a,它就是Bool。但是没有关于a 是什么的信息。由于parsed 是多态的,parsed 的每次使用都可以为a 选择不同的类型,因此查看其他使用站点也无济于事。有了单态限制,这正是发生的情况:parsed 被限制为单态,因此必须找到适用于所有用途的 a 选择,并且 fst (parsed !! 0) 组合与 Integer unpackNum 的结果类型然后执行它。
  • 现在我明白了。同一个 let 块中的两个站点可以被视为两个实例,其中一个绑定对另一个绑定没有影响,这太疯狂了。我赞成您的回答并接受了@acomar 的回答,因为您认为他的案例是最好的解决方案(避免了两个站点的问题)。谢谢。
  • @TingL 在这种情况下,在两个不同的实例中处理两个调用可能看起来很奇怪。但是,在某些情况下,您确实想使用两个实例,例如let f = ("value=" ++) . show in (f (length [1,2]), f 'a').
  • @TingL:我最近遇到的另一个例子涉及read 本身。 read :: Read a => String -> a 所以read "11" :: Read a => a。您可以在两个不同的上下文中使用它,并让它返回两个不同的值,尽管引用透明。因此,在您明确告诉它不能这样做之后,编译器不能使用 read 的不同使用站点作为单一单态类型的证据。
【解决方案2】:

类型应该统一,但不存在NoMonomorphismRestriction(正如@FedorGogolev 和@kosmikus 在cmets 中指出的那样)。但是,以下更惯用的方法在任何情况下都不需要类型注释:

data LispVal = Atom String
             | List [LispVal]
             | DottedList [LispVal] LispVal
             | Number Integer
             | String String
             | Bool Bool
unpackNum :: LispVal -> Integer
unpackNum (Number n) = n
unpackNum (String n) = case reads n of
                           [] -> 0
                           ((x, _):xs) -> x
unpackNum (List [n]) = unpackNum n
unpackNum _ = 0

大小写与空值的区别

归结为null 是一个函数,而case 是直接语法。

null :: [a] -> Bool

因此,启用 -XNoMonomorphismRestriction 后,在提供参数时,它会尽可能多态。该函数不以任何方式限制参数类型,因此编译器无法确定reads 的返回类型,从而导致错误。在函数调用的地方,类型是不明确的。在case 语句的情况下,编译器可以使用整个表达式,模式匹配也可以优化reads 的返回类型。

【讨论】:

  • 那是一个快速的downvote...愿意分享为什么?如果答案不正确或需要改进,我很乐意改进。
  • 为什么没有评论就被否决了?我看不出答案有问题。 +1 平衡 ;)
  • 感谢您的意见。我不知道为什么要投反对票。不过我确实有一个问题。 (如果我错了,请纠正我)type ReadS a = String -> [(a,String)],所以这对中的snd 被称为String,不是吗?
  • NoMonomorphismRestriction
  • 好吧,那么问题的真正答案是:如果单态限制被禁用,parsed的定义得到一个多态类型,即Read a => [(a, String)],然后在@987654335中第一次使用@ 没有足够的上下文信息来解析 a 是什么。 (注意刚刚发生的事情:这实际上一个在某种程度上需要单态性限制的示例。这些情况非常罕见,但它们确实发生了......)
猜你喜欢
  • 2014-09-10
  • 1970-01-01
  • 1970-01-01
  • 2017-05-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-26
  • 1970-01-01
相关资源
最近更新 更多