Haskell 多参数typeclass实例声明

Haskell 多参数typeclass实例声明,haskell,Haskell,我试图理解多参数类型类,但我没有得到实例声明。我开始尝试为向量类型创建一个InnerProductSpace类型类,这样我就可以对两个向量执行点积。首先,我只是想看看是否可以将每个向量的第一个元素相乘。这是我的密码 class InnerProductSpace a b c where dot :: a -> b -> c data Vector = Vector [Double] deriving (Show) instance InnerProductSpace Vect

我试图理解多参数类型类,但我没有得到实例声明。我开始尝试为向量类型创建一个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

我做错了什么?谢谢

问题在于,编译器不知道如何在已知的情况下选择正确的实例。如果你尝试像

vectorA `dot` vectorA
编译器开始搜索右边的
dot
,知道它的类型必须是
dot::Vector->Vector->c0
。不幸的是,这本身并没有足够的信息-
c0
可以是任何东西,而且编译器从不认为仅仅因为它只有一个实例,它就必须是正确的实例(这与开放世界的假设有关——编译器可能还没有看到另一个实例,因此它更愿意抛出一个错误)。你可以通过明确地告诉编译器结果应该是什么来逃避它

vectorA `dot` vectorB :: Double
但是这很单调,而且可能会在使用数字类型时失败很多,因为这些类型通常也是泛型的

(vectorA `dot` vectorB) + 3
我们知道它是双精度的,但编译器无法证明这一点

一种解决方案是使用@AndrewC在评论中建议的类型族。实际上,我强烈建议这样做。另一种解决方案是函数依赖关系。它们是这样编写的

class InnerProductSpace a b c | a b -> c where
  dot :: a -> b -> c
并翻译为向编译器承诺:“知道
a
b
就足以唯一地识别
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
但是promise为编译器提供了足够的信息来解析上一个示例中的
Double

Main*> (Vector [1,2,3]) `dot` (Vector [2,3,4]) + 3.0
5
或者使用TypeFamilies

class (D a b ~ c) => InnerProductSpace a b c where
  type D a b
  dot :: a -> b -> c


多参数类型类充满了概念上的障碍,而类型族在类型级别像函数一样工作,因此更容易。看看我对有类似问题的人的回答,以及我建议他们如何使用类型族来处理向量空间。谢谢!现在我将坚持使用函数依赖关系。这对我来说更有意义现在还没有。这是我和Haskell在一起的第一天,所以也许明天我会准备好处理类型族;-)难道不知道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)