具有Haskell功能依赖性的不明确类型变量

时间:2012-05-04 11:29:02

标签: haskell types typeclass functional-dependencies

我正在使用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不知道使用哪个零,因为它可以是任何零,而是产生任何反过来被输入~-的东西,我们只知道它有一个{{1}在它的正确参数中产生a

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

a

我认为neg n = (zero :: Add a b c) ~- n ab这里 abc形成了周围的类,但是任何ab和c,所以我怎么能表达一个类型,它是对本地类型变量的引用?

2 个答案:

答案 0 :(得分: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可以是来自zero的{​​{1}},来自Add Int Int Int的{​​{1}} ,无论你是从默认实现,实例声明还是普通代码中引用它,都无法消除两者之间的歧义。

(您可能反对来自zero的{​​{1}}和来自Add Int Double Double的{​​{1}}具有相同的值,但编译器无法知道某人不是'我将在另一个模块中定义zero,并在那里给Add Int Int Int一个不同的值。)

通过将类型类拆分为两个,我们消除了歧义。

答案 1 :(得分:3)

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

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

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

我不明白为什么你甚至需要Invertible中的ZeroAdd约束;你可以随时添加数字而不知道它们的零值,不是吗?为什么要neg作为~+的要求;有一些数字应该是可添加的,而不是它们可以否定(例如自然数)?只需将课堂问题分开。