2012-04-04 11 views
5

Estoy intentando escribir algún código Haskell en el que haya múltiples tipos de datos, cada uno de los cuales puede tener múltiples implementaciones. Para hacer esto, defino cada tipo de datos como class cuyos métodos son los constructores y selectores relevantes, y luego implemento todas las operaciones en los miembros de esa clase en términos de los constructores y selectores dados.¡Diversión con tipos! Resolución de declaraciones de instancias múltiples

Por ejemplo, tal vez A es una clase polinomio (con métodos getCoefficients y makePolynomial) que pueden tener una representación como SparsePoly o una DensePoly y B es una clase número complejo (con métodos getReal, getImag y makeComplex) que puede ser representado como ComplexCartesian o ComplexPolar.

He reproducido un ejemplo mínimo a continuación. Tengo dos clases A y B, cada una de las cuales tiene una implementación. Quiero hacer todas las instancias de ambas clases en instancias de Num automáticamente (esto requiere las extensiones de tipo FlexibleInstances y UndecidableInstances). Esto funciona bien cuando sólo tengo uno de A o B, pero cuando intento compilar con ambos, me sale el siguiente error:

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 

supongo que el mensaje de los 'declaraciones de instancias duplicadas' se debe a un tipo de datos podría hacerse una instancia de ambos A y B. Quiero ser capaz de hacer una promesa al compilador de que no haré eso, o posiblemente especifique una clase predeterminada para usar en el caso de que un tipo sea una instancia de ambas clases.

¿Hay alguna manera de hacer esto (quizás otra extensión de tipo?) ¿O es algo con lo que estoy atascado?

Aquí está mi código:

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

Editar: Para quedado claro, no estoy tratando de escribir cualquier código de práctica utilizando esta técnica. Lo hago como ejercicio para ayudarme a comprender mejor el sistema de tipos y las extensiones.

+4

Relacionado: [Cómo escribo, "si typeclass a, then a también es una instancia de b según esta definición."] (Http://stackoverflow.com/a/3216937/98117). – hammar

Respuesta

11

Esta parte de su pregunta

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.

Es incorrecto. En realidad es porque usted ha escrito dos casos,

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

que el compilador no puede decir aparte (ver el enlace del comentario de @ Hammar, contextos de clase no cuentan con el propósito de diferenciar entre declaraciones de instancias).

Una solución es agregar un tipo de testigo.

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

El compilador puede usar los tipos de testigo para diferenciar entre las declaraciones de su instancia.

+0

Gracias, este es el enfoque que he seguido. –

4

No hay una forma realmente buena de hacerlo; la mejor práctica consiste en definir algunas constantes como

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

lo que hace que la escritura de los Num casos más mecánica después de tener un A ejemplo:

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

¡Gracias! Puedo ver cómo esto sería útil si solo tienes un pequeño número de implementaciones de cada clase. –

Cuestiones relacionadas