Spaß mit Typen! Mehrere Instanzdeklarationen auflösen

Ich versuche, einen Haskell-Code zu schreiben, in dem es mehrere Datentypen gibt, von denen jeder mehrere Implementierungen haben kann. Dazu definiere ich jeden Datentyp alsclass deren Methoden die relevanten Konstruktoren und Selektoren sind, und dann alle Operationen auf Mitgliedern dieser Klasse in Bezug auf die angegebenen Konstruktoren und Selektoren implementieren.

Zum Beispiel vielleichtA ist eine Polynomklasse (mit MethodengetCoefficients undmakePolynomial), die eine Darstellung alsSparsePoly oder einDensePoly undB ist eine komplexe Zahlenklasse (mit MethodengetReal, getImag undmakeComplex), die als dargestellt werden kannComplexCartesian oder einComplexPolar.

Ich habe ein minimales Beispiel unten reproduziert. Ich habe zwei KlassenA undB Jedes davon hat eine Implementierung. Ich möchte alle Instanzen beider Klassen zu Instanzen von machenNum automatisch (dies erfordert dieFlexibleInstances undUndecidableInstances Typerweiterungen). Dies funktioniert gut, wenn ich nur eine vonA oderBWenn ich jedoch versuche, mit beiden zu kompilieren, wird die folgende Fehlermeldung angezeigt:

<code>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
</code>

Ich nehme an, dass die Meldung "Instanzdeklarationen duplizieren" vorliegt, weil ein Datentyp zu einer Instanz von beiden gemacht werden könnteA undB. Ich möchte dem Compiler versprechen, dass ich das nicht tue, oder möglicherweise eine Standardklasse angeben, die verwendet werden soll, wenn ein Typ eine Instanz beider Klassen ist.

Gibt es eine Möglichkeit, dies zu tun (vielleicht eine andere Typerweiterung?) Oder ist dies etwas, mit dem ich festgefahren bin?

Hier ist mein Code:

<code>{-# 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
</code>

Bearbeiten: Um mich klar zu machen, versuche ich nicht, mit dieser Technik praktischen Code zu schreiben. Ich mache es als Übung, um das Typensystem und die Erweiterungen besser zu verstehen.

Antworten auf die Frage(2)

dies zu tun. Am besten definieren Sie einige Konstanten wie

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

das macht das schreibenNum Instanzen mechanischer, nachdem Sie eine habenA Beispiel:

<code>instance A Foo where ...
instance Num x => Num (Foo x) where
    (+) = plusA
    (-) = minusA
</code>
 Chris Taylor05. Apr. 2012, 09:43
Vielen Dank! Ich kann sehen, wie nützlich dies wäre, wenn Sie nur eine kleine Anzahl von Implementierungen für jede Klasse haben.
Lösung für das Problem

Ich nehme an, dass die Meldung "Instanzdeklarationen duplizieren" darauf zurückzuführen ist, dass ein Datentyp zu einer Instanz von A und B gemacht werden könnte. Ich möchte dem Compiler versprechen, dass ich das nicht tue, oder möglicherweise a angeben Standardklasse für den Fall, dass ein Typ eine Instanz beider Klassen ist.

ist falsch. Es ist eigentlich, weil Sie zwei Instanzen geschrieben haben,

<code>instance Num (a x)
instance Num (b x)
</code>

dass der Compiler nicht unterscheiden kann (siehe den Link aus @ hammars Kommentar, Klassenkontexte zählen nicht für die Unterscheidung zwischen Instanzdeklarationen).

Eine Lösung besteht darin, einen Zeugentyp hinzuzufügen.

<code>{-# 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)
</code>

Der Compiler kann die Zeugenarten verwenden, um zwischen Ihren Instanzdeklarationen zu unterscheiden.

 Chris Taylor05. Apr. 2012, 09:42
Danke, das ist der Ansatz, den ich gewählt habe.

Ihre Antwort auf die Frage