空列表测试中的类型不明确的变量

空列表测试中的类型不明确的变量

问题描述:

请考虑以下代码段,该代码段定义了一个函数 foo ,该函数接收一个列表并对该列表执行某些操作(例如排序).我试图将代码段加载到 ghci 中:

Consider the following snippet which defines a function foo which takes in a list and performs some operation on the list (like sorting). I tried to load the snippet in ghci:

-- a function which consumes lists and produces lists
foo :: Ord a => [a] -> [a]
foo [] = []
foo (x:xs) = xs

test1 = foo [1, 2, 3] == [2, 3]
test2 = null $ foo []

却发生以下错误:

No instance for (Ord a0) arising from a use of ‘foo’
    The type variable ‘a0’ is ambiguous
    Note: there are several potential instances:
      instance (Ord a, Ord b) => Ord (Either a b)
        -- Defined in ‘Data.Either’
      instance forall (k :: BOX) (s :: k). Ord (Data.Proxy.Proxy s)
        -- Defined in ‘Data.Proxy’
      instance (GHC.Arr.Ix i, Ord e) => Ord (GHC.Arr.Array i e)
        -- Defined in ‘GHC.Arr’
      ...plus 26 others
    In the second argument of ‘($)’, namely ‘foo []’
    In the expression: null $ foo []
    In an equation for ‘test2’: test2 = null $ foo []

问题出在表达式 test2 = null $ foo [] 中.此外,从 foo 的类型定义中删除 Ord a 约束将解决此问题.奇怪的是,在 interactive 模式下(在加载 foo 的定义之后)键入 null $ foo [] (正常工作)并可以产生预期的.

The problem is in the expression test2 = null $ foo []. Furthermore, removing Ord a constraint from the type definition of foo will solve the problem. Strangely, typing null $ foo [] in the interactive mode (after loading the definition for foo) works correctly and produces the expected true.

我需要对此行为进行清楚的解释.

I need a clear explanation for this behaviour.

我喜欢以字典传递样式"来考虑类型类.签名

I like thinking of typeclasses in "dictionary-passing style". The signature

foo :: Ord a => [a] -> [a]

说, foo 接受了一个 Ord a 方法的字典(基本上作为参数)和一个 a s的列表,并给出返回 a 的列表.字典中有类似(<):: a->之类的东西.a->布尔及其表亲.当我们调用 foo 时,我们需要提供这样的字典.这是由编译器隐式完成的.所以

says that foo takes a dictionary of methods for Ord a, essentially as a parameter, and a list of as, and gives back a list of as. The dictionary has things in it like (<) :: a -> a -> Bool and its cousins. When we call foo, we need to supply such a dictionary. This is done implicitly by the compiler. So

foo [1,2,3]

将使用 Ord Integer 字典,因为我们知道 a Integer .

will use the Ord Integer dictionary, because we know that a is Integer.

但是,在 foo [] 中,该列表可以是任何内容的列表-没有确定该类型的信息.但是我们仍然需要找到 Ord 字典以传递给 foo (尽管您的 foo 根本不使用它,但签名指出:它可以,而且这很重要).这就是为什么存在模棱两可的类型错误的原因.您可以手动指定类型,这样将提供足够的信息来填充字典,例如

However, in foo [], the list could be a list of anything -- there is no information to determine the type. But we still need to find the Ord dictionary to pass to foo (although your foo doesn't use it at all, the signature says that it could, and that's all that matters). That's why there is an ambiguous type error. You can specify the type manually, which will give enough information to fill in the dictionary, like this

null (foo ([] :: [Integer]))

或带有新的 TypeApplications 扩展名

null (foo @Integer [])

如果您删除了 Ord 约束,则正如您所观察到的那样,它起作用了,这仅仅是因为我们不再需要提供字典.我们不再需要知道 a 是什么特定类型来调用 foo (这对我来说有点神奇:-).

If you remove the Ord constraint, it works, as you have observed, and this is just because we no longer need to supply a dictionary. We don't need to know what specific type a is to call foo anymore (this feels a little magical to me :-).

请注意, foo([] :: Ord a => [a])不会消除歧义,因为尚不清楚哪个特定的您想通过的Ord 字典;是 Ord Int 还是 Ord(也许是字符串)等?没有 no 通用 Ord 词典,因此我们必须选择,在这种情况下,对于选择哪种类型也没有 no 规则.而当您说(Ord a,Num a)=>时,[a] ,然后默认指定一种选择方式,我们选择 Integer ,因为这是 Num 类的特例.

Note that foo ([] :: Ord a => [a]) does not eliminate the ambiguity, because it is not known which specific Ord dictionary you want to pass; is it Ord Int or Ord (Maybe String), etc.? There is no generic Ord dictionary, so we have to choose, and there is no rule for what type to choose in this case. Whereas when you say (Ord a, Num a) => [a], then defaulting specifies a way to choose, and we pick Integer, since it is a special case of the Num class.

foo [] ghci 中起作用的事实归因于 ghci 类型默认值一般而言,这当然不是Haskell最漂亮的部分,但是在您要问的各种极端情况下,它会大量出现.

The fact that foo [] works in ghci is due to ghci’s extended defaulting rules. It might be worth reading about type defaulting in general, which is surely not the prettiest part of Haskell, but it is going to come up a lot in the kinds of corner cases you are asking about.