3

我在玩 Haskell 的 FunctionalDependencies-Extension 以及 MultiParamTypeClasses。我定义了以下内容:

class Add a b c | a b -> c where
    (~+) :: a -> b -> c
    (~-) :: a -> b -> c
    neg :: a -> a
    zero :: a

效果很好(我尝试过使用 Int 和 Double 的实例,最终目标是能够在不显式转换的情况下添加 Int 和 Doubles)。

当我尝试为 neg 或 (~-) 定义默认实现时,如下所示:

class Add ...
    ...
    neg n = zero ~- n

GHCi (7.0.4) 告诉我以下内容:

Ambiguous type variables `a0', `b0', `c0' in the constraint:
  (Add a0 b0 c0) arising from a use of `zero'
Probable fix: add a type signature that fixes these type variable(s)
In the first argument of `(~-)', namely `zero'
In the expression: zero ~- n
In an equation for `neg': neg n = zero ~- n

Ambiguous type variable `a0' in the constraint:
  (Add a0 a a) arising from a use of `~-'
Probable fix: add a type signature that fixes these type variable(s)
In the expression: zero ~- n
In an equation for `neg': neg n = zero ~- n

我想我确实理解这里的问题。GHC 不知道要使用哪个零,因为它可以是任何零,产生任何东西,而这些东西又被输入到~-我们只知道的 a 中,它有一个a正确的论点并产生一个a.

那么我如何指定它应该是来自同一个实例的零,即我如何表达如下内容:

neg n = (zero :: Add a b c) ~- n

我认为a,bchere不是周围类的 abc ,而是任何 ab 和 c ,那么我该如何表达一个对局部类型变量的引用的类型呢?

4

2 回答 2

6

拉出一个negzero使用一种类型的超类:

class Zero a where
    neg :: a -> a
    zero :: a

class Zero a => Add a b c | a b -> c where
    (~+) :: a -> b -> c
    (~-) :: a -> b -> c

关键是您的方式zero :: Int可能是fromzerofromAdd Int Int Int并且无论您是从默认实现或实例声明还是普通代码中引用它,都无法消除两者之间的歧义。zeroAdd Int Double Double

(您可能会反对zerofromAdd Int Int IntzerofromAdd Int Double Double将具有相同的值,但编译器无法知道有人不会Add Int Char Bool在不同的模块中定义并在zero那里给出不同的值。)

通过将类型类一分为二,我们消除了歧义。

于 2012-05-04T11:52:52.593 回答
3

您不能将zero函数表达为Add类的一部分。类声明中的所有类型变量都必须在类的每个函数的类型声明中遇到;否则,Haskell 将无法决定使用哪个类型实例,因为它的约束太少。

换句话说,zero它不是您正在建模的类的属性。您基本上是在说:“对于任何三种类型a, b, c,该类型必须存在一个零值a”,这是没有意义的;你可以选择任何一个bandc它会解决问题,因此bandc完全无法使用,所以如果你有 anAdd Int Int Int或 an Add Int (Maybe String) Boat,Haskell 不知道更喜欢哪个实例。您需要将否定和“零性”的属性分成单独的类型类:

class Invertible a where
  invert :: a -> a

neg :: Invertible a => a -> a
neg = invert

class Zero a where
  zero :: a

class Add a b c | a b -> c where
  (~+) :: a -> b -> c
  (~-) :: a -> b -> c

我不明白为什么你甚至需要 ; 中的InvertibleandZero约束Add。你总是可以在不知道它们的零值的情况下添加数字,不是吗?为什么要表达neg为要求~+;有一些数字应该是可加的而不是可以否定的(例如自然数)?只需将类关注点分开即可。

于 2012-05-04T11:56:23.097 回答