多参数类型类实例声明

6

我正在努力理解多参数类型类,但我不理解实例声明。我开始尝试为向量类型创建一个InnerProductSpace类型类,以便我可以对两个向量执行点积运算。首先我只想看看能否将每个向量的第一个元素相乘。以下是我的代码:

class InnerProductSpace a b c where
dot :: a -> b -> c

data Vector = Vector [Double]
  deriving (Show)

instance InnerProductSpace Vector Vector Double where
  dot (Vector a) (Vector b) = (head a * head b)

试图使用dot函数后的错误如下:
No instance for (InnerProductSpace Vector Vector c0)
  arising from a use of `dot'
The type variable `c0' is ambiguous
Possible fix: add a type signature that fixes these type variable(s)
Note: there is a potential instance available:
  instance InnerProductSpace Vector Vector Double
    -- Defined at Vector.hs:8:10
Possible fix:
  add an instance declaration for
  (InnerProductSpace Vector Vector c0)
In the expression: dot a b
In an equation for `it': it = dot a b

我做错了什么?谢谢!


6
多参数类型类存在概念上的难点,而类型族在类型层面上像函数一样运作,因此更容易理解。请参考我对某个类似问题的回答这里,以及我如何建议他们使用类型族来处理向量空间。 - AndrewC
2个回答

7
问题在于编译器不知道如何根据已知信息选择正确的实例。如果尝试以下内容:
vectorA `dot` vectorA

编译器会寻找正确的dot,并知道其类型必须为dot :: Vector -> Vector -> c0。不幸的是,仅凭这些信息还不足够——c0可以是任何东西,编译器永远不会认为只有一个实例就一定是正确的实例(这与开放世界假设有关——可能还有另一个实例在外面等着被发现,因此它更喜欢报错)。您可以通过明确告诉编译器结果应该是什么来解决这个问题。

vectorA `dot` vectorB :: Double

但这样做很繁琐,而且由于数字类型通常也是泛型的,所以很容易失败。例如,在这里使用了哪个 Num 实例?

(vectorA `dot` vectorB) + 3

我们知道它是“Double”,但编译器无法证明。
一种解决方案是使用类型族,就像@AndrewC在评论中建议的那样。我实际上强烈推荐这样做。在野外你会看到另一个解决方案是功能依赖关系。写法如下:
class InnerProductSpace a b c | a b -> c where
  dot :: a -> b -> c

并且翻译要包含对编译器的承诺:“知道ab就足以唯一地确定c”。编译器会让你保持诚实,防止你编写违反该承诺的实例。

instance InnerProductSpace Vector Vector Double where
  dot (Vector a) (Vector b) = (head a * head b)

instance InnerProductSpace Vector Vector Float where
  dot (Vector a) (Vector b) = (head a * head b)

---

/Users/tel/tmp/foo.hs:10:10:
    Functional dependencies conflict between instance declarations:
      instance InnerProductSpace Vector Vector Double
        -- Defined at /Users/tel/tmp/foo.hs:10:10
      instance InnerProductSpace Vector Vector Float
        -- Defined at /Users/tel/tmp/foo.hs:13:10

但是这个声明会给编译器提供足够的信息来解析前面例子中的 Double

Main*> (Vector [1,2,3]) `dot` (Vector [2,3,4]) + 3.0
5

1
谢谢!目前我会坚持使用函数依赖。这对我现在来说更有意义。这是我第一天使用 Haskell,也许明天我准备好处理类型族了。;-) - user2658647
仅凭知道a和b,就足以唯一确定c了吗? - jk.

3
或者使用TypeFamilies。
class (D a b ~ c) => InnerProductSpace a b c where
  type D a b
  dot :: a -> b -> c

或者

class InnerProductSpace a b where
  type D a b :: *
  dot :: a -> b -> D a b

instance InnerProductSpace Vector Vector where
  type D Vector Vector = Double
  dot (Vector a) (Vector b) = (head a * head b)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接