Haskell: class 实例的类型变量不明确

Haskell: type variable is ambiguous for class instance

import qualified Prelude

class List list where
  toList :: list a -> [a]
  fromList :: [a] -> list a

data Deque a = Deque [a] [a]

instance List Deque where
  toList (Deque xs sy) = xs ++ reverse sy

  fromList xs = Deque ys (reverse zs)
    where (ys, zs) = splitAt (length xs `div` 2) xs

我收到如下复制的错误。似乎 GHCI 没有将 ys 检测为双端队列,而是检测为 class 列表的一般实例。

ghci> xs = [2, 3, 4]
ghci> ys = fromList xs
ghci> (Deque a b) = ys
ghci> toList (Deque a b)
[2,3,4]
ghci> toList ys

<interactive>:5:1: error:
    * Could not deduce (List list0) arising from a use of `toList'
      from the context: Num a
        bound by the inferred type of it :: Num a => [a]
        at <interactive>:5:1-9
      The type variable `list0' is ambiguous
      These potential instance exist:
        instance [safe] List Deque -- Defined at main.hs:12:10
    * In the expression: toList ys
      In an equation for `it': it = toList ys
ghci> let xs = [2, 3, 4]
ghci> let ys = fromList ys

此时值具有可能的最通用类型:

ghci> :t xs
xs :: Num a => [a]
ghci> :t ys
ys :: (List list, Num a) => list a

没有 Deque 的提示,这是有道理的,因为您还没有提到任何双端队列。

但是那些变量保持不变,它们不会改变它们的类型!

之后

ghci> (Deque a b) = ys
ghci> toList (Deque a b)
[2,3,4]

您对 ys 的情况仍然相同,即

ghci> :t ys
ys :: (List list, Num a) => list a

当然,您曾经将 ys 用作双端队列,这是它的一种使用方式。但是作为多态,它也可以是任何其他 List 类型,并且 GHCi 不会做出任何猜测它应该是什么。但是你当然可以告诉它:

ghci> toList (ys :: Deque Int)
[2,3,4]

或更短

ghci> :set -XTypeApplications 
ghci> toList @Deque ys
[2,3,4]

这是按设计工作的。当你写:

λ> ys = fromList xs

分配给 ys 的类型是:

λ> :t ys
ys :: (List list, Prelude.Num a) => list a

也就是说,ys 是一个多态值,可以“是”任何类型的 List

因此,当您将其绑定到特定模式时:

λ> Deque a b = ys

然后 ys 被实例化为 Deque 并且适当的值被绑定到 ab,但这不会使 ys 单态.也就是说,ys 并没有突然“变成”Deque。相反,它仍然是一个多态值,可以重新绑定到其他 List。例如,如果您还有一个普通旧列表的实例:

instance List [] where
  toList = id
  fromList = id

您可以在多种类型上实例化 ys

λ> xs = [2, 3, 4]
λ> ys = fromList xs
λ> Deque a b = ys     -- instantiate as Deque
λ> c:d = ys           -- instantiate as []

如果您觉得这很奇怪,请考虑:

λ> id "hello"
λ> id 'x'

两者都有效,即使第一次使用多态值 id :: a -> a 时,它被实例化为 String -> String,而第二次使用时,它被实例化为 Char -> Char。道理是一样的。