10 Stimmen

Haskell: Überschneidende Instanzen

Betrachten Sie das folgende Beispielprogramm:

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 kann nur verwenden get anstelle von lift get denn instance MonadState s m => MonadState s (MaybeT m) ist im MaybeT-Modul definiert.

Viele solcher Instanzen sind in einer Art kombinatorischer Explosion definiert.

Es wäre schön gewesen (obwohl unmöglich? warum?), wenn wir die folgende Typklasse hätten:

{-# LANGUAGE MultiParamTypeClasses #-}

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

Lassen Sie uns versuchen, sie als solche 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

Verwendung von lifts $ print i anstelle von liftIO $ print i funktioniert, was gut ist.

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

GHC (6.10.3) gibt den folgenden Fehler aus:

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 kann verstehen, warum " instance SuperMonad a a " gilt. Aber warum glaubt GHC, dass das andere auch gilt?

37voto

Norman Ramsey Punkte 193087

Um an die ausgezeichnete Antwort von ephemient anzuknüpfen: Haskell-Typklassen verwenden ein Open-World-Annahme : Irgendein Idiot kann später auftauchen und eine Instanzdeklaration hinzufügen, die kein Duplikat und doch überschneidet sich mit Ihre Instanz. Betrachten Sie es als ein Spiel mit Gegnern Wenn ein Angreifer Ihr Programm mehrdeutig machen kann, meckert der Compiler.

Wenn Sie GHC verwenden, können Sie dem Compiler natürlich sagen: "Zur Hölle mit deiner Paranoia; erlaube mir meine zweideutige Instanzdeklaration":

{-# LANGUAGE OverlappingInstances #-}

Wenn die spätere Entwicklung Ihres Programms zu einer Überlastauflösung führt, die Sie nicht erwartet haben, erhält der Compiler 1.000 "Ich hab's ja gesagt"-Punkte :-)

Abschreibungsvermerk

Dieses Pragma ist seit GHC 7.10 veraltet, und stattdessen sollten pro-Instanz-Pragmas verwendet werden. Weitere Details finden Sie in der GHC-Dokumentation .

8voto

ephemient Punkte 189038

Nur weil Sie in Ihrem aktuellen Modul keine Instanz definiert haben, heißt das nicht, dass nicht irgendwo anders eine definiert werden könnte.

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

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

Angenommen, Ihr Modul und SomeOtherModule sind in einem einzigen Programm miteinander verknüpft.

Beantworten Sie nun folgende Frage: Verwendet Ihr Code

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

?

4voto

Iceland_jack Punkte 5422

Wenn Sie sich überschneidende Instanzen haben, versuchen Sie, deren Verhalten an newtype s:

type    SuperEgo :: (k -> Type) -> (k -> Type)
newtype SuperEgo m a = SuperEgo (m a)

type    Elevator :: (k -> k1 -> Type) -> (k -> k1 -> Type)
newtype Elevator trans m a = Elevator (trans m a)

instance SuperMonad m (SuperEgo m) where
  lifts :: m ~> SuperEgo m
  lifts = SuperEgo

instance (SuperMonad m super, Monad super, MonadTrans trans) => SuperMonad m (Elevator trans super) where
  lifts :: m ~> Elevator trans super
  lifts = Elevator . lift . lifts

Monaden können nun ableiten über SuperEgo M um Identitätsinstanzen zu erhalten

{-# Language DerivingVia #-}

data Ok a = Ok a
  deriving (SuperMonad Ok)
  via SuperEgo Ok

Da die Definition eines Monad-Transformers eher mühsam ist, zeige ich, wie man eine Lifting-Instanz für einen bestehenden Monad-Transformator wie StateT s . Dabei wird eine eigenständige Ableitung verwendet, die ausführlicher ist, da Sie den Klassenkontext selbst ausfüllen müssen:

deriving
  via Elevator (StateT s) super
  instance (Monad super, SuperMonad m super) => SuperMonad m (StateT s super)

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X