2012-04-04 13 views
5

Sto cercando di scrivere del codice Haskell in cui ci sono più tipi di dati, ognuno dei quali può avere più implementazioni. Per fare questo, definisco ogni tipo di dati come un class i cui metodi sono i costruttori ei selettori rilevanti e quindi implementano tutte le operazioni sui membri di quella classe in termini di costruttori e selettori specifici.Divertimento con i tipi! Risoluzione esempio più dichiarazioni

Per esempio, forse A è una classe polinomio (con metodi getCoefficients e makePolynomial) che possono avere una rappresentazione come SparsePoly o un DensePoly e B è una classe numero complesso (con metodi getReal, getImag e makeComplex) che può essere rappresentato come ComplexCartesian o ComplexPolar.

ho riprodotto un esempio minimo al di sotto. Ho due classi A e B ognuna delle quali ha un'implementazione. Voglio fare tutte le istanze di entrambe le classi in istanze di Num automaticamente (questo richiede le estensioni di FlexibleInstances e UndecidableInstances). Questo funziona bene quando ho solo una delle A o B, ma quando provo a compilare con entrambi, ottengo il seguente errore:

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 

suppongo che il messaggio 'dichiarazioni di istanza duplicati' è perché un tipo di dati potrebbe essere creata un'istanza di entrambi A e B. Voglio essere in grado di fare una promessa al compilatore che non lo farò, o forse specificare una classe predefinita da utilizzare nel caso in cui un tipo sia un'istanza di entrambe le classi.

C'è un modo per fare questo (forse un altro tipo di estensione?) O si tratta di qualcosa che mi sono bloccato con?

Ecco il mio codice:

{-# 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: per intenderci, non sto cercando di scrivere alcun codice pratico utilizzando questa tecnica. Lo faccio come un esercizio per aiutarmi a capire meglio il tipo di sistema e le estensioni.

+4

Correlato: [Come scrivere, "se il tipo a, allora a è anche un'istanza di b in base a questa definizione."] (Http://stackoverflow.com/a/3216937/98117). – hammar

risposta

11

Questa parte della tua domanda

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.

non è corretto. In realtà è perché hai scritto due istanze,

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

che il compilatore non può distinguere (vedi il link dal commento di @ Hammar, contesti di classe non contano ai fini della differenziazione tra le dichiarazioni di istanza).

Una soluzione è quella di aggiungere un tipo di testimone.

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

Il compilatore può utilizzare i tipi di testimoni per distinguere tra le dichiarazioni di istanza.

+0

Grazie, questo è l'approccio con cui sono andato. –

4

Non c'è davvero un buon modo per fare questo; la migliore pratica è quella di definire alcune costanti come

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

che rende la scrittura dei Num casi più meccanico dopo si dispone di un A esempio:

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

Grazie! Posso vedere come ciò sarebbe utile se hai solo un piccolo numero di implementazioni di ogni classe. –

Problemi correlati