2012-04-04 7 views
5

Ich versuche, einen Haskell-Code zu schreiben, in dem es mehrere Datentypen gibt, von denen jeder mehrere Implementierungen haben kann. Um dies zu tun, definiere ich jeden Datentyp als class, deren Methoden die relevanten Konstruktoren und Selektoren sind, und dann alle Operationen auf Mitgliedern dieser Klasse in Bezug auf die gegebenen Konstruktoren und Selektoren implementieren.Spaß mit Typen! Auflösen mehrerer Instanzdeklarationen

beispielsweise vielleicht A ein Polynom Klasse (mit Methoden getCoefficients und makePolynomial), die eine Darstellung als SparsePoly oder DensePoly und B ist eine komplexe Zahl Klasse (getReal mit Methoden, getImag und makeComplex) aufweisen kann, der sein kann, dargestellt als ComplexCartesian oder ComplexPolar.

Ich habe ein minimales Beispiel unten wiedergegeben. Ich habe zwei Klassen A und B von denen jede eine Implementierung hat. Ich möchte alle Instanzen beider Klassen automatisch in Instanzen von Num setzen (dies erfordert die Typenerweiterungen und). Das funktioniert gut, wenn ich nur eine von A oder B habe, aber wenn ich versuche, mit beiden zu kompilieren, erhalte ich folgende Fehlermeldung:

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 

Ich nehme an, dass die ‚doppelte Instanz Erklärungen‘ Nachricht ist, weil ein Datentyp könnte eine Instanz von A und B gemacht werden. Ich möchte dem Compiler versprechen können, dass ich das nicht tun werde, 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 Art Erweiterung?) Oder ist das etwas, mit dem ich festhalte?

Hier ist mein 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 

Edit: Um mich klar, ich versuche, keinen praktischen Code zu schreiben, mit dieser Technik. Ich mache es als eine Übung, um mir zu helfen, das Typsystem und Erweiterungen besser zu verstehen.

+4

Verwandte: [Wie schreibe ich, "wenn Typklasse A, dann ist auch eine Instanz von b durch diese Definition."] (Http://StackOverflow.com/a/3216937/98117). – hammar

Antwort

11

Dieser Teil Ihrer Frage

I suppose that the 'duplicate instance declarations' message is because a data type could be made an instance of both A and B. I want to be able to make a promise to the compiler that I won't do that, or possibly specify a default class to use in the case that a type is an instance of both classes.

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

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

, dass der Compiler nicht auseinanderhalten kann (siehe den Link von @ hammar Kommentar, Klasse Kontexte zählen nicht zum Zweck zwischen Instanz Erklärungen Differenzierung).

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

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

Der Compiler kann die Witness-Typen verwenden, um zwischen Ihren Instanzdeklarationen zu unterscheiden.

+0

Danke, das ist der Ansatz, mit dem ich gegangen bin. –

4

Es gibt keinen wirklich guten Weg, dies zu tun; die beste Praxis ist es, einige Konstanten wie

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

, die macht das Schreiben der Num Instanzen mehr mechanische, nachdem Sie haben eine A Instanz zu definieren:

instance A Foo where ... 
instance Num x => Num (Foo x) where 
    (+) = plusA 
    (-) = minusA 
+0

Danke! Ich kann sehen, wie das nützlich wäre, wenn Sie nur eine kleine Anzahl von Implementierungen jeder Klasse haben. –