4

对不起,令人困惑的标题。我在 Haskell 中编写一个解析器组合库是为了好玩。以下是所有(我认为!)相关的类型注释和定义:

data Parser a = Parser (State -> Reply a)

parse :: Parser a -> [Char] -> Either ParseError a

nil :: Parser [a]
nil = Parser $ \state -> Ok [] state

基本上,该parse函数将 aParser包裹的函数应用于当前状态,如果解析成功,则将结果包裹在Either. nil解析器获取一个状态并返回对空列表的成功解析。所以我们应该有,

parse nil "dog" == Right []

事实上,如果我只是加载所有这些模块所在的模块,那么它会编译并且计算结果为 True。

不过,我实际上是在尝试在库上运行一些 QuickCheck 测试,所以我写了这个:

import Parsimony
import Test.QuickCheck

prop_nil :: [Char] -> Bool
prop_nil xs = parse nil xs == Right []

这无法编译!它抛出以下错误:

No instance for (Eq a0) arising from a use of `=='
The type variable `a0' is ambiguous

在这一点上,我很困惑为什么表达式在评估时可以正常工作,但无法在参数化版本中编译。

4

3 回答 3

9

因为nil是多态的,Right []也是多态的 GHC 有一个类型的表达式Bool,但中间有一些未绑定的类型变量。GHC 因不知道使用哪种具体类型而倒下并死亡。GHCi 无论好坏,都会[()]因为其默认规则而推断或类似的东西。这是 ghci 的怪癖之一,它会自动默认类型变量。

a要解决此问题,只需手动强制绑定

-- It's important that whatever you force it to actually is comparable
-- eg there should be an instance like
instance Eq ParseError where
-- Otherwise you're kinda stuck.

prop_nil xs = parse nil xs == (Right xs :: Either ParseError String)

PS我喜欢Parsimony这个解析器库的名字,祝你好运!

于 2013-07-22T05:07:47.887 回答
7

问题是 的类型nilParser [a]parse nil xs类型也是如此Either ParseError [a]Right []最常见的类型是Either l [a]; 将其与parse nil xs强制lto be 进行比较ParseError,但列表中的类型仍然完全不受约束。没有更多上下文,它仍然是完全多态的;那a不一定是Eq类型类的成员,即使它是没有办法知道使用哪个实例来实现,因此在这两个术语上==调用是无效的。==

在一个现实的程序中,你可能会因为你将结果用于某些事情而避免这种情况,这将迫使该特定事件与你使用它的任何事情保持一致。那可能是一些具体类型,它具有Eq.

当您谈论加载模块时,我认为您的意思是在 GHCI 解释器中。GHCI 添加了一些额外的默认规则。特别是它倾向于将不受约束的类型变量(不是顶级函数的类型)默认为(),因此它不必经常抱怨模棱两可的类型变量。

GHCi 中的交互式会话比完整编译的实际模块更容易遇到模棱两可的类型变量,因为它必须大部分独立地编译小片段。GHCi 扩展了默认规则以使它们更频繁地工作(尽管当用户期望不同的类型时,它通常只会将错误延迟到下一个引用,并且 GHCi 和 GHC 之间的差异经常导致混淆)。

测试片段可能会遇到类似的问题。如果您正在测试多态函数,您通常不会像在真正有目的地使用该函数时那样对某些类型进行足够的约束以使类型推断起作用。但是如果没有 GHCi 的扩展默认规则,这个问题就会在问题的位置表现为一个实际的模棱两可的类型错误,而不是通过任意选择一个类型来掩盖它。

要解决这个问题,您只需要添加一个类型注释来修复列表的类型。要么声明parse nil xsor的完整类型Right [],只需在右侧声明空列表文字的类型。像这样的东西应该可以解决问题:

prop_nil :: [Char] -> Bool
prop_nil xs = parse nil xs == Right ([] :: [Int])
于 2013-07-22T05:21:52.057 回答
3

另一种方法是首先避免 Eq 约束:

prop_nil xs = either (const False) null (parse nil xs) 

或者,更明确地说

prop_nil xs = case parse nil xs of
   Right [] -> True
   _        -> False
于 2013-07-22T08:24:13.470 回答