2009-06-30 11 views
8

Si consideri il seguente programma di esempio:Haskell: sovrapposizione casi

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() 

Si può usare solo get invece di lift get perché instance MonadState s m => MonadState s (MaybeT m) è definito nel modulo MaybeT.

Molte di queste istanze sono definite in modo di esplosione combinatoria.

Sarebbe stato bello (anche se impossibile perché?), Se abbiamo avuto la seguente tipo di classe:

{-# LANGUAGE MultiParamTypeClasses #-} 

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

Proviamo a definirlo come tale:

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

Utilizzando lifts $ print i invece di liftIO $ print i funziona, che è bello.

Ma l'utilizzo di lifts (get :: StateT Int IO Int) invece di (get :: MaybeT (StateT Int IO) Int) non funziona.

GHC (6.10.3) dà il seguente errore:

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) 

Posso capire perché "instance SuperMonad a a" si applica. Ma perché GHC pensa che anche l'altro lo faccia?

risposta

35

al follow-up risposta eccellente del ephemient: classi di tipo Haskell utilizzano un open-world assunto: qualche idiota può venire dopo e aggiungere un dichiarazione di istanza che è non è un duplicato eppure sovrapposizioni con l'istanza. Pensa ad esso come ad un gioco avversario: se un avversario può rendere ambiguo il tuo programma, il compilatore saettare.

Se stai usando GHC potete naturalmente dicono al compilatore "al diavolo con la vostra paranoia; mi permetta la mia dichiarazione ambigua esempio":

{-# LANGUAGE OverlappingInstances #-} 

Se in seguito l'evoluzione del programma porta a sovraccaricare la risoluzione non ti aspettavi, il compilatore ottiene 1.000 punti I-said-you-so :-)

+3

+1 per una formulazione eccellente. –

+0

grazie! seguendo il tuo contributo sono riuscito a farlo! – yairchu

+4

Sovrapposizioni Le istanze sono molto più in basso nella mia lista di estensioni (anche oltre le UndecidableInstances, che rendono il lavoro del compilatore molto più difficile) da usare - non solo non trasportabili, ma anche rompere le garanzie di sicurezza normalmente fornite da Haskell. Suggerirei OP a succhiarlo e gestire 'lift'ing o no' lift'ing manualmente, piuttosto che aggiungere questo hack in ... ma questa è la mia opinione. – ephemient

8

Solo perché non è stata definita un'istanza nel modulo corrente non significa che non si possa definire altrove.

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

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

Supponiamo che il vostro modulo e SomeOtherModule sono collegati tra loro in un unico programma.

Ora, rispondere a questa domanda: fa il vostro utilizzo del codice

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

grazie. ma sono ancora confuso: da solo, le mie istanze non si sovrappongono, ma qualcuno può definire un'istanza che potrebbe sovrapporre le mie istanze. qualcuno non può sempre definire un'istanza da sovrapporre alla mia istanza? – yairchu

+2

La struttura rende impossibile per il compilatore determinare in modo univoco quale istanza deve essere utilizzata nel codice. Se potesse essere risolto in modo inequivocabile, allora una sovrapposizione altrove non avrebbe importanza. – ephemient

+0

@ephemient: questo è dovuto al modo specifico in cui funziona il compilatore, giusto? Dico questo poiché posso determinare senza ambiguità quale istanza può essere utilizzata. – yairchu