Haskell"无法演绎..."

时间:2017-08-22 21:55:16

标签: haskell

我是Haskell的初学者,正在实施一些基本的代数,比如

class (Eq g) => AbelianGroup g where
    gplus :: g -> g -> g
    gnegate :: g -> g 
    gzero :: g
    gminus :: g -> g -> g
    gminus a b = gplus a (gnegate b)
    gmult :: Integer -> g -> g

class (AbelianGroup d) => IntegralDomain d where
    idtimes :: d -> d -> d
    idone :: d            

{- you can ignore upper code, it is added only for sake of completeness -}

class (IntegralDomain d) => EuclideanDomain d where
    edeg :: d -> Integer
    edivision :: d -> d -> (d, d)
    egcd :: d -> d -> d
    egcd f g | g == gzero = f 
             | f == gzero = g
             | otherwise = let (q, r) = edivision f g in egcd g r

class (IntegralDomain f) => Field f where
    finvert :: f -> f
    fdivide :: f -> f -> f
    fdivide a b = idtimes a (finvert b)

instance (Field f, IntegralDomain f) => EuclideanDomain f where
    edeg x = 0
    edivision x y = (fdivide x y, gzero)

当我收到错误时

* Could not deduce (Field d) arising from a use of `edivision'
  from the context: EuclideanDomain d
  bound by the class declaration for `EuclideanDomain'
  at ...
  Possible fix:
  add (Field d) to the context of
  the class declaration for `EuclideanDomain'
* In the expression: edivision f g
  In a pattern binding: (q, r) = edivision f g
  In the expression: let (q, r) = edivision f g in egcd g r

错误来自 - 如错误陈述 - 来自" edivision f g"在dafault定义" egcd"在" EuclideanDomain"。因为我是Haskell的新手,我的问题是

  

为什么会出现此错误?

如果我将(字段d)放在EuclideanDomain的声明中,则此错误消失。但当然,整个代码变得毫无用处(并非每个欧几里德域都是Field等。)

1 个答案:

答案 0 :(得分:2)

tl; dr 你可能不想做你想做的事。它在类型系统中创建了太多的复杂性。简单的解决方案是不要将实例编写为通用的实例,而不是您要编写的实例。

长版

instance (Field f, IntegralDomain f) => EuclideanDomain f where

你不想这样做。我不相信Haskell默认会让你这样做(你可能已经开启了一些编译器扩展以使它在某些时候起作用)。你在这里说的是“每个领域都是欧几里德领域,就像所说的那样”。如果有人出现并创建了新类型Foo,他们可能希望为EuclideanDomainField定义一个实例,但如果他们这样做,那么有两个EuclideanDomain实例对于同一类型。

这是一个问题。你可以告诉GHC忽略这个问题,只希望用OverlappingInstances编译器扩展来解决问题,但就像我说的那样,你可能不希望这样做,因为它会让你的代码更加混乱。您可能还需要FlexibleInstances以及其他一些可能需要一个通用的类型类实例来传递类型检查器。现在,你有两个选择。

选项1

让睡觉的狗撒谎,并假设用户足够聪明,可以同时实施FieldEuclideanDomain。如果你想这样做,你可以通过提供“默认”功能让他们变得容易,如果他们真的想要从EuclideanDomain派生Field,他们可以将它们复制到他们的实例声明中。

edegDefault :: Field f => f -> Integer
edegDefault x = 0
edivisionDefault :: Field f => f -> f -> (f, f)
edivisionDefault x y = (fdivide x y, gzero)

然后用户可以自己实现EuclideanDomain。如果他们想要自己实现这些功能,他们可以自由地实现这些功能,但如果他们不这样做,他们总是可以这样做。

instance EuclideanDomain f where
    edeg = edegDefault
    edivision = edivisionDefault

选项2

您不时会看到的另一个选项更适用于用户可能真正忘记实施EuclideanDomain的情况。在这种情况下,我们会将Field个实例包装在newtype中,并声明newtypeEuclideanDomain

newtype WrappedField f = WrappedField f

instance Field f => EuclideanDomain (WrappedField f) where
    ...

然后,您仍然可以在不干扰用户实例空间的情况下获得功能。这种模式可以在Haskell标准库中看到。在最长的时间内,Monad由于历史原因不是Applicative的子类,尽管它应该是数学上的。因此,Haskell语言设计人员实现了一个WrappedMonad newtype,它采用Monad实例并为其提供了Applicative实例。