2009-06-30 17 views
8

Considere el siguiente programa de ejemplo:Haskell: casos superpuestos

next :: Int -> Int 
next i 
    | 0 == m2 = d2 
    | otherwise = 3 * i + 1 
    where 
    (d2, m2) = i `divMod` 2 

loopIteration :: MaybeT (StateT Int IO)() 
loopIteration = do 
    i <- get 
    guard $ i > 1 
    liftIO $ print i 
    modify next 

main :: IO() 
main = do 
    (`runStateT` 31) . runMaybeT . forever $ loopIteration 
    return() 

Sólo puede utilizar get en lugar de lift get porque instance MonadState s m => MonadState s (MaybeT m) se define en el módulo de MaybeT.

Muchas de estas instancias se definen en una especie de explosión combinatoria.

Hubiera sido agradable (aunque imposible por qué??) Si tuviéramos el siguiente tipo de clase:

{-# LANGUAGE MultiParamTypeClasses #-} 

class SuperMonad m s where 
    lifts :: m a -> s a 

Vamos a tratar de definirlo como tal:

{-# LANGUAGE FlexibleInstances, ... #-} 

instance SuperMonad a a where 
    lifts = id 

instance (SuperMonad a b, MonadTrans t, Monad b) => SuperMonad a (t b) where 
    lifts = lift . lifts 

Usando lifts $ print i vez de liftIO $ print i funciona, lo cual es bueno.

Pero usar lifts (get :: StateT Int IO Int) en lugar de (get :: MaybeT (StateT Int IO) Int) no funciona.

GHC (6.10.3) da el siguiente error:

Overlapping instances for SuperMonad 
          (StateT Int IO) (StateT Int IO) 
    arising from a use of `lifts' 
Matching instances: 
    instance SuperMonad a a 
    instance (SuperMonad a b, MonadTrans t, Monad b) => 
      SuperMonad a (t b) 
In a stmt of a 'do' expression: 
    i <- lifts (get :: StateT Int IO Int) 

puedo ver por qué "instance SuperMonad a a" se aplica. Pero, ¿por qué GHC piensa que el otro también lo hace?

Respuesta

35

Para dar seguimiento excelente respuesta de ephemient: clases de tipos de Haskell utilizan un suposición de mundo abierto: un idiota puede venir después y añadir una declaración de instancia que es no un duplicado y sin embargo se superpone con su instancia. Piense en ello como un juego de adversario: si un adversario puede hacer que su programa sea ambiguo, el compilador babea.

Si está utilizando GHC por supuesto puede decir que el compilador "al diablo con su paranoia; me permite mi declaración de instancia ambigua":

{-# LANGUAGE OverlappingInstances #-} 

Si posterior evolución de su programa conduce a la sobrecarga de resolución no esperaba, el compilador obtiene 1,000 puntos de I-told-you-so :-)

+3

+1 para una excelente redacción. –

+0

gracias! siguiendo su entrada, ¡he logrado hacerlo! – yairchu

+4

Las superposiciones están muy lejos en mi lista de extensiones (incluso más allá de las IndecidiblesInstancias, lo que simplemente hace que el trabajo del compilador sea mucho más difícil), no solo no es portátil, sino que también rompe las garantías de seguridad que normalmente proporciona Haskell. Aconsejaría a OP que lo succionara y tratara de 'levantar' o no 'levantar' manualmente, en lugar de agregar este truco ... pero esa es mi opinión. – ephemient

8

El hecho de que no haya definido una instancia en su módulo actual no significa que no pueda definirse en otro lugar.

{-# LANGUAGE ... #-} 
module SomeOtherModule where 

-- no practical implementation, but the instance could still be declared 
instance SuperMonad (StateT s m) m 

Suponga que su módulo y SomeOtherModule están unidos entre sí en un mismo programa.

Ahora, responder a esta: usa su código

instance SuperMonad a a 
    -- with a = StateT Int IO 

o

instance (SuperMonad a b, MonadTrans t, Monad b) => SuperMonad a (t b) 
    -- with a = StateT Int IO 
    --  t = StateT Int 
    --  b = IO 

?

+0

gracias. pero todavía estoy confundido: por mi cuenta, mis instancias no se superponen, pero alguien puede definir una instancia que haga que mis instancias se superpongan. ¿Alguien puede definir siempre una instancia para superponerse con mi instancia? – yairchu

+2

Su estructura hace que sea imposible para el compilador determinar inequívocamente qué instancia usar en su código. Si pudiera resolverse sin ambigüedades, una superposición en otro lugar no importaría. – ephemient

+0

@ephemient: eso se debe a la forma específica en que funciona el compilador, ¿no? Lo digo porque puedo determinar inequívocamente qué instancia se puede usar. – yairchu