2010-10-22 11 views
7

(Lo siento con anticipación si la pregunta es estúpida u obvia, no tengo mucha experiencia con Haskell).En Haskell, ¿hay alguna forma de expresar que un tipo debe ser una instancia de una clase de tipo en más de una forma?

¿Hay alguna manera de expresar que un tipo debe ser una instancia de una clase de tipo en más de una forma? Esto se ilustra mejor con un ejemplo (que es probablemente algo tonto): en matemáticas, podemos decir que un semiencogido es un conjunto que es un monoide conmutativo en una operación (que llamaremos adición, identidad 0) y un monoide bajo otro (que llamaremos multiplicación) junto con los requisitos que la multiplicación distribuye sobre la suma y que 0 aniquila todos los elementos bajo multiplicación. Las últimas partes no son importantes aquí.

Supongamos ahora que tienen una clase de tipos Monoid (que no debe confundirse con Data.Monoid),

class Monoid m where 
    unit :: m 
    operation :: m -> m -> m 

y me gustaría crear una clase de tipos Semiring. De la definición dada anteriormente, me gustaría decir "si el tipo r es un monoide en dos (distintas formas), lo llamaremos semiautomático". Así que me gustaría algo como

class (Monoid r, Monoid r) => Semiring r where ... 

que por supuesto no funciona. Ciertamente, el ejemplo se vuelve un poco extraño ya que no hay más funciones que quisiéramos requerir para las semirredes, por lo que la clase de tipo estaría vacía, pero espero que ilustre lo que estoy preguntando (o simplemente pretender que necesitamos alguna función). f:r->r para Semiring r).

Así, en el contexto general, estoy pidiendo: Dada una clase de tipos A, ¿hay una manera de parametrizar una clase de tipos B a con el requisito de que a sea una instancia de A de dos maneras (lo que significa que deben aplicar el a funciones especificadas por A de dos maneras)?

+0

Gracias a todos los que han respondido hasta ahora. Casi todas las respuestas podrían ser aceptadas, pero fui con la que obtuvo el mayor número de votos. – gspr

Respuesta

11

Una opción es definir sus propias monoides para las dos operaciones de semiring:

class AdditiveMonoid m where 
    zero :: m 
    (<+>) :: m -> m -> m 

class MultiplicativeMonoid m where 
    one :: m 
    (<*>) :: m -> m -> m 

y luego combinarlas:

class (MultiplicativeMonoid m, AdditiveMonoid m) => Semiring m 

El problema es que no se puede expresar las leyes monoides o el hecho de que una operación es conmutativa. Lo mejor que puede obtener es definir propiedades de comprobación rápida para las leyes.

Para obtener inspiración, consulte el documento numeric prelude y this.

+0

¡Muchas gracias! Parece que la respuesta corta es "no, tienes que duplicar el código", entonces? Estoy al tanto del preludio numérico, pero es tan enorme que puede ser difícil de aprender cuando eres nuevo. Sin embargo, ese documento parece muy interesante, ¡gracias! – gspr

7

Para Monoid, específicamente, esto se hace con envoltorios de tipo. Si nos fijamos en el módulo Data.Monoid, se encuentran dos estructuras diferentes para monoidales Bool valores: Any y All, así como dos estructuras diferentes para los tipos que implementan Num: Sum y Product y dos estructuras de Maybe tipos: First y Last.

Vas a tener problemas con su ejemplo semiring, sin embargo, ya que las estructuras de monoidales Sum y Product tanto proporcionar implementaciones de mempty (versión Haskell de su unit) y mappend (versión Haskell de su operation).

+0

Hmm ... gracias por la información, pero parece que la respuesta se reduce a "no", entonces? Es una lástima :-( – gspr

3

Consulte también la publicación de "sección rítmica" de Conor McBride: http://www.haskell.org/pipermail/libraries/2008-January/008917.html, aunque está en el nivel de valor y no ayuda con las clases de texto.

biblioteca monoides de Kmett (antes de que se quitó la materia Anillo) implementado algo parecido al enfoque de Daniel Velkov: http://hackage.haskell.org/package/monoids-0.1.36

debo añadir que el bueno de este enfoque es que mediante la definición de aditivo y multiplicativo en un tipo de datos claramente, puedes capturar que no son lo mismo, es decir, que este último se distribuye sobre el primero.

2

La técnica común para esto es, como otras respuestas mencionan, envoltorios newtype. En muchos casos, esto me parece una especie de abuso del concepto de clase de tipo. Las clases de tipos son "axiomas" lógicos que establecen que algunos hechos son ciertos para un tipo; por ejemplo, que Tal vez es una Mónada, o que Int es un Num, o que las listas están ordenadas cuando están sus elementos. A menudo, como en el caso de Eq y Ord, hay otras definiciones razonables pero las elegidas son de alguna manera "canónicas". Otras veces, como en el caso de Monoid, no hay.

En el caso de Monoid y otras estructuras muy abstractas como esta, soy de la opinión que una declaración data sería más útil. Por ejemplo, data Monoid a = Monoid {mempty :: a ; mappend :: a -> a -> a}. Entonces, tenemos addition :: Num a => Monoid a, liftMaybe :: Monoid a -> Monoid (Maybe a), etc.

La misma técnica podría usarse para implementar su Semiring. Por ejemplo (usando un tipo de datos Monoid como antes): data Semiring a = Semiring { addition, multiplication :: Monoid a }.

+0

De hecho, si entiendo las cosas correctamente, esto es básicamente lo que GHC hace bajo el capó para implementar el polimorfismo de clase de tipo. No estoy convencido del beneficio en el usuario nivel de código, sin embargo, –

4

Otras respuestas han mencionado newtype envoltorios, pero no se da una solución explícita de usarlos:

-- export these newtypes from the module defining Semiring 
newtype Add a = Add a 
newtype Multiply a = Multiply a 

class (Monoid (Add a), Monoid (Multiply a)) => Semiring a where 
    -- empty 

instance Monoid (Add Integer) where 
    unit = Add 0 
    Add a `operation` Add b = Add (a + b) 

-- etc. 

que necesitará algunas extensiones de GHC como FlexibleContexts.

+0

Gracias por el código explícito de muestra. Lo hace todo mucho más claro. Aunque, me queda un poco la sensación de que a menudo me confunden cuando uno debe hacer clases de tipos y cuando uno debe hacer tipos. De hecho, para superar el problema en cuestión, había creado * typeclasses * Additive y Multiplicative. Tu camino parece más agradable, pero eso significa que tengo un entendimiento erróneo de la relación entre lo que "debería" ser clases de tipos y lo que "debería" ser tipos . Tal vez Haré otra pregunta :-) – gspr

Cuestiones relacionadas