2009-06-30 4 views
8

Betrachten Sie das folgende Beispielprogramm:Haskell: Überlappende Instanzen

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

Es nur get statt lift get verwenden können, weil instance MonadState s m => MonadState s (MaybeT m) im MaybeT Modul definiert ist.

Viele solcher Instanzen sind in Art einer kombinatorischen Explosionsweise definiert.

Es wäre schön gewesen (wenn auch nicht warum?), Wenn wir die folgende Typ-Klasse haben:

{-# LANGUAGE MultiParamTypeClasses #-} 

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

Lassen sie versuchen, es als solches zu definieren:

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

Mit lifts $ print i statt von liftIO $ print i funktioniert, das ist schön.

Aber mit lifts (get :: StateT Int IO Int) statt (get :: MaybeT (StateT Int IO) Int) funktioniert nicht.

GHC (6.10.3) gibt den folgenden Fehler:

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) 

ich sehen kann, warum "instance SuperMonad a a" gilt. Aber warum denkt GHC, dass der andere es auch tut?

Antwort

35

Um ephemient die ausgezeichnete Antwort folgen zu lassen. Betrachten Sie es als ein Gegner-Spiel: Wenn ein Gegner Ihr Programm mehrdeutig machen kann, blökt der Compiler.

Wenn Sie GHC verwenden können Sie natürlich sagen dem Compiler „zur Hölle mit Paranoia, erlauben Sie mir, meine zweideutige Instanz Erklärung“:

{-# LANGUAGE OverlappingInstances #-} 

Spätere Entwicklung des Programms führt Auflösung zu überlasten Du hast nicht erwartet, der Compiler bekommt 1.000 I-thought-you-so Punkte :-)

+3

+1 für ausgezeichnete Formulierung. –

+0

danke! Nach Ihrer Eingabe habe ich es geschafft! – yairchu

+4

OverlappingInstances sind weit entfernt auf meiner Liste der Erweiterungen (sogar noch weiter als UndecidableInstances, was die Arbeit des Compilers nur viel schwieriger macht) - nicht nur unportabel, sondern auch die Sicherheitsgarantien zu brechen, die Haskell normalerweise bietet. Ich würde OP raten, es aufzusaugen und sich damit zu befassen, Lift zu heben oder nicht manuell zu heben, anstatt diesen Hack hinzuzufügen ... aber das ist meine Meinung. – ephemient

8

Nur weil Sie in Ihrem aktuellen Modul keine Instanz definiert haben, bedeutet das nicht, dass Sie nicht anderswo definiert werden können.

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

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

Ihr Modul Angenommen und SomeOtherModule werden zusammen in einem einzigen Programm verknüpft. Jetzt

, diese Frage beantworten: Hat Ihr Code Verwendung

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

oder

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

? Haskell Typ-Klassen verwenden, um eine Open-World-Annahme: irgendein Idiot kommen kann später und fügen Sie eine Instanz Erklärung, dass ist kein Duplikat und noch Überschneidungen mit Instanz

+0

danke. aber ich bin immer noch verwirrt: allein überlappen sich meine Instanzen nicht, aber jemand kann eine Instanz definieren, die meine Instanzen überlappen lässt. kann nicht immer jemand eine Instanz definieren, die sich mit meiner Instanz überschneidet? – yairchu

+2

Ihre Struktur macht es dem Compiler unmöglich, eindeutig zu bestimmen, welche Instanz in Ihrem Code verwendet wird. Wenn es eindeutig aufgelöst werden könnte, würde eine Überlappung an anderer Stelle keine Rolle spielen. – ephemient

+0

@ephemient: Das liegt an der speziellen Funktionsweise des Compilers, oder? Ich sage das, da ich eindeutig bestimmen kann, welche Instanz verwendet werden kann. – yairchu