5

我正在尝试编写一些 Haskell 代码,其中有多种数据类型,每种数据类型都可以有多个实现。为此,我将每个数据类型定义class为其方法是相关的构造函数和选择器,然后根据给定的构造函数和选择器实现对该类成员的所有操作。

例如,也许A是一个多项式类(带有方法getCoefficientsmakePolynomial),可以表示为 aSparsePoly或 a DensePoly,并且B是一个复数类(带有方法getReal和) getImagmakeComplex可以表示为 aComplexCartesian或 a ComplexPolar

我在下面复制了一个最小的例子。我有两个类AB每个类都有一个实现。我想将两个类的所有实例都Num自动转换为实例(这需要FlexibleInstancesUndecidableInstances类型扩展)。当我只有一个Aor时,这可以正常工作B,但是当我尝试同时使用两者进行编译时,会出现以下错误:

Duplicate instance declarations:
  instance [overlap ok] (A a, Num x, Show (a x), Eq (a x)) =>
                        Num (a x)
    -- Defined at test.hs:13:10-56
  instance [overlap ok] (B b, Num x, Show (b x), Eq (b x)) =>
                        Num (b x)
    -- Defined at test.hs:27:10-56

我想“重复的实例声明”消息是因为数据类型可以同时成为A和的实例B。我希望能够向编译器承诺我不会这样做,或者可能指定一个默认类以在类型是两个类的实例的情况下使用。

有没有办法做到这一点(可能是另一种类型扩展?)或者这是我坚持的事情?

这是我的代码:

{-# LANGUAGE FlexibleInstances, UndecidableInstances, OverlappingInstances #-}

class A a where
    fa :: a x -> x
    ga :: x -> a x

data AImpl x = AImpl x deriving (Eq,Show)

instance A AImpl where
    fa (AImpl x) = x
    ga x = AImpl x

instance (A a, Num x, Show (a x), Eq (a x)) => Num (a x) where
    a1 + a2 = ga (fa a1 + fa a2)
    -- other implementations go here


class B b where
    fb :: b x -> x
    gb :: x -> b x

data BImpl x = BImpl x deriving (Eq,Show)

instance B BImpl where
    fb (BImpl x) = x
    gb x = BImpl x

instance (B b, Num x, Show (b x), Eq (b x)) => Num (b x) where
    -- implementations go here

编辑:为了让自己清楚,我不想使用这种技术编写任何实用的代码。我这样做是为了帮助自己更好地理解类型系统和扩展。

4

2 回答 2

12

你问题的这一部分

我想“重复实例声明”消息是因为可以将数据类型作为 A 和 B 的实例。我希望能够向编译器承诺我不会这样做,或者可能指定一个在类型是两个类的实例的情况下使用的默认类。

是不正确的。实际上是因为你写了两个实例,

instance Num (a x)
instance Num (b x)

编译器无法区分(请参阅@hammar评论中的链接,类上下文不计入区分实例声明的目的)。

一种解决方案是添加见证类型。

{-# LANGUAGE FlexibleInstances, FlexibleContexts, UndecidableInstances, OverlappingInstances #-}

data AWitness

data AImpl witness x = AImpl x deriving (Eq,Show)

instance A (AImpl AWitness) where
    fa (AImpl x) = x
    ga x = AImpl x

instance (A (a AWitness), Num x, Show (a AWitness x), Eq (a AWitness x)) => Num (a AWitness x) where
    a1 + a2 = ga (fa a1 + fa a2)

编译器可以使用见证类型来区分您的实例声明。

于 2012-04-04T17:24:38.220 回答
5

没有真正的好方法可以做到这一点。最好的做法是定义一些常量,比如

plusA, minusA :: (A a, Num x) => a x -> a x -> a x

这使得Num在您拥有A实例后编写实例更加机械:

instance A Foo where ...
instance Num x => Num (Foo x) where
    (+) = plusA
    (-) = minusA
于 2012-04-04T16:50:17.540 回答