2010-05-20 16 views
9

Decir que tengo dos clases de tipos definidos de la siguiente manera que son idénticos en función pero diferente en los nombres:Vinculación/Combinación de clases de tipos en Haskell

class Monad m where 
    (>>=) :: m a -> (a -> m b) -> m b 
    return :: a -> m a 

class PhantomMonad p where 
    pbind :: p a -> (a -> p b) -> p b 
    preturn :: a -> p a 

¿Hay una manera de atar estas dos clases juntos para algo que es una instancia de PhantomMonad será automáticamente una instancia de Monad, o las instancias de cada clase deberán escribirse explícitamente? Cualquier idea sería muy apreciada, ¡gracias!

+2

Is 'preturn :: a -> p b' a typo? –

Respuesta

13

Buena respuesta: No, lo que espera hacer no es realmente viable. Puede escribir una instancia que parece que hace lo que desea, posiblemente necesitando algunas extensiones de GHC en el proceso, pero no funcionará de la manera que le gustaría.

Respuesta poco inteligente: Probablemente pueda lograr lo que quiera utilizando una metaprogramación a nivel de tipo aterradora, pero puede ser complicado. Esto realmente no se recomienda a menos que absolutamente necesite esto para funcionar por alguna razón.

Las instancias oficiales no pueden depender realmente de otras instancias, porque GHC solo mira el "encabezado de instancia" cuando toma decisiones, y las restricciones de clase están en el "contexto". Para hacer algo así como un "sinónimo de clase de tipo" aquí, tendría que escribir lo que parece una instancia de Monad para todos los tipos posibles, lo cual obviamente no tiene sentido. Se superpondrá con otras instancias de Monad, que tiene sus propios problemas.

Además de eso, no creo que una instancia así cumpla con los requisitos de verificación de terminación para la resolución de instancia, por lo que también necesitaría la extensión UndecidableInstances, lo que significa la capacidad de escribir instancias que enviarán el tipo de GHC verificador en un bucle infinito.

Si realmente quieres ir por ese agujero de conejo, busca un poco en Oleg Kiselyov's website; él es una especie de patrona de la metaprogramación de nivel de tipo en Haskell.

Es algo divertido, sin duda, pero si solo quieres escribir el código y hacerlo funcionar, probablemente no valga la pena.

Edit: De acuerdo, en retrospectiva he exagerado el problema aquí. Algo como PhantomMonad funciona bien como una opción única y debe hacer lo que quiera, dado el Overlapping - y UndecidableInstances extensiones de GHC. Las cosas complicadas comienzan cuando quieres hacer algo mucho más complicado que lo que está en la pregunta. Mi más sincero agradecimiento a Norman Ramsey por haberme llamado, realmente debería haberlo sabido.

Todavía no realmente recomiendo haciendo este tipo de cosas sin una buena razón, pero no es tan malo como lo hice sonar. Mea culpa.

+0

Gracias! Tenía curiosidad si me estaba perdiendo algo obvio con mi pensamiento enrevesado o si esto realmente fue tan jodido. No hace falta decir que me estoy quedando con la respuesta "buena". – thegravian

+0

@thegravian: Una decisión sabia. Si ayuda, su idea no es intrínsecamente absurda, simplemente no es factible dado el sistema de clases tipo de Haskell tal como está. Creo que ha habido algunas extensiones propuestas que lo harían funcionar, pero ninguna se ha implementado hasta ahora. –

+0

@camcann: La solución no es realmente todo * tan * aterrador, ¿verdad?Quiero decir, la palabra "indecidible" da un poco de miedo, pero la verificación de tipo Haskell ya está completa por tiempo exponencial, así que no dejaría que eso me impida hacer algo que realmente quisiera hacer ... –

6

Es un diseño inusual. ¿No puedes simplemente eliminar el PhantomMonad, ya que es isomorfo para la otra clase?

+0

Tiene razón, el diseño no es viable (y es realmente inútil). Decidí evitar hacer esto, pero aún tenía curiosidad si Haskell tenía algún tipo de instalaciones para hacer esto y me echaba de menos. Probablemente no, porque esto es algo sensato que la gente no haría. – thegravian

+3

casos similares serían la clase 'MonadTrans' de' mtl' y 'transformers'. son exactamente lo mismo. una respuesta sobre cómo unificar clases (si hubiera sido posible) en este simple ejemplo esotérico también podría haber sido tremendamente útil para el código real. – yairchu

+2

@yairchu: Para ser justos, la solución de Don de "eliminar una clase" es la solución correcta para ese caso, es decir, eliminar 'mtl' ... –

1

Aunque este sentido realmente no tiene, trate

instance Monad m => PhantomMonad m where 
    pbind = (>>=) 
    preturn = return 

(tal vez con algunas advertencias del compilador desactivadas).

+0

Eso es al revés de lo que él pidió, sin embargo. Por supuesto, 'instance (PhantomMonad m) => Monad m where ...' causará aún más problemas. –

7

¿Hay alguna forma de unir estas dos clases, por lo que algo que es una instancia de PhantomMonad será automáticamente una instancia de Monad?

Sí, pero requiere las extensiones de lenguaje poco alarmantes FlexibleInstances y UndecidableInstances:

instance (PhantomMonad m) => Monad m where 
    return = preturn 
    (>>=) = pbind 

FlexibleInstances no es tan malo, pero el riesgo de indecidibilidad es un poco más alarmante. El problema es que en la regla de inferencia, nada es cada vez más pequeño, por lo que si combina esta declaración de instancia con otra similar (como decir la dirección inversa), podría hacer que el verificador de tipos buclee para siempre.

En general, me siento cómodo usando FlexibleInstances, pero tiendo a evitar UndecidableInstances sin una buena razón. Aquí estoy de acuerdo con la sugerencia de Don Stewart de que sería mejor usar Monad para empezar. Pero su pregunta tiene más que ver con la naturaleza de un experimento mental, la respuesta es que puede hacer lo que quiera sin entrar en un nivel de miedo de Oleg.

+0

Esto no funciona igual porque se ha superpuesto cualquier otra instancia de 'Monad' . Pero después de jugar tratando de romper cosas, parece que GHC es más inteligente de lo que pensaba con clases de tipo de parámetro único. Obviamente, solo obtienes una instancia genérica como esta, pero como única, funciona bien dado 'OverlappingInstances'. –

3

Otra solución es usar newtype. Esto no es exactamente lo que quiere, pero a menudo se usa en tales casos.

Esto permite vincular diferentes formas de especificar la misma estructura. Por ejemplo, ArrowApply (de Control.Arrow) y Monad son equivalentes. Puede usar Kleisli para hacer un ArrowApply de una mónada, y ArrowMonad para hacer una mónada de ArrowApply.

También, son posibles los envoltorios unidireccionales: WrapMonad (en Control.Applicative) forma un aplicativo de una mónada.

class PhantomMonad p where 
    pbind :: p a -> (a -> p b) -> p b 
    preturn :: a -> p a 

newtype WrapPhantom m a = WrapPhantom { unWrapPhantom :: m a } 

newtype WrapReal m a = WrapReal { unWrapReal :: m a } 

instance Monad m => PhantomMonad (WrapPhantom m) where 
    pbind (WrapPhantom x) f = WrapPhantom (x >>= (unWrapPhantom . f)) 
    preturn = WrapPhantom . return 

instance PhantomMonad m => Monad (WrapReal m) where 
    WrapReal x >>= f = WrapReal (x `pbind` (unWrapReal . f)) 
    return = WrapReal . preturn 
+1

No se pudo encontrar un 'WrapMonad' en' Control.Arrow' pero 'instancia ArrowApply a => Monad (a())'. 'newtype Kleisli m a b = Kleisli (a -> m b)'; 'instancia Monad m => ArrowApply (Kleisli m)'; Haga un viaje de ida y vuelta a través de estas instancias y no termine en el mismo 'ArrowApply' con el que comenzó. 'pre a x y = a x y',' publicar a x y = x -> a() y'. – yairchu

+0

Vaya, me refiero a ArrowMonad. – sdcvvc

Cuestiones relacionadas