我是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等。)
答案 0 :(得分:2)
tl; dr 你可能不想做你想做的事。它在类型系统中创建了太多的复杂性。简单的解决方案是不要将实例编写为通用的实例,而不是您要编写的实例。
instance (Field f, IntegralDomain f) => EuclideanDomain f where
你不想这样做。我不相信Haskell默认会让你这样做(你可能已经开启了一些编译器扩展以使它在某些时候起作用)。你在这里说的是“每个领域都是欧几里德领域,就像我所说的那样”。如果有人出现并创建了新类型Foo
,他们可能希望为EuclideanDomain
和Field
定义一个实例,但如果他们这样做,那么有两个EuclideanDomain
实例对于同一类型。
这是一个问题。你可以告诉GHC忽略这个问题,只希望用OverlappingInstances
编译器扩展来解决问题,但就像我说的那样,你可能不希望这样做,因为它会让你的代码更加混乱。您可能还需要FlexibleInstances
以及其他一些可能需要一个通用的类型类实例来传递类型检查器。现在,你有两个选择。
让睡觉的狗撒谎,并假设用户足够聪明,可以同时实施Field
和EuclideanDomain
。如果你想这样做,你可以通过提供“默认”功能让他们变得容易,如果他们真的想要从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
您不时会看到的另一个选项更适用于用户可能真正忘记实施EuclideanDomain
的情况。在这种情况下,我们会将Field
个实例包装在newtype
中,并声明newtype
为EuclideanDomain
。
newtype WrappedField f = WrappedField f
instance Field f => EuclideanDomain (WrappedField f) where
...
然后,您仍然可以在不干扰用户实例空间的情况下获得功能。这种模式可以在Haskell标准库中看到。在最长的时间内,Monad
由于历史原因不是Applicative
的子类,尽管它应该是数学上的。因此,Haskell语言设计人员实现了一个WrappedMonad
newtype,它采用Monad
实例并为其提供了Applicative
实例。