2016-08-05 19 views
3

Ich habe eine sehr nützliche freie Monade aus einem Summen-Datentyp erstellt. Der Abstracts Zugang zu einem persistenten Datenspeicher:MonadError-Instanz für eine freie Monade

data DataStoreF next = 
    Create Asset       (String -> next) 
    | Read  String       (Asset -> next) 
    | Update Asset       (Bool -> next) 
    | UpdateAll [Asset]       (Bool -> next) 
    | Delete Asset       (Bool -> next) 
    | [...] -- etc. etc. 
    | Error  String 

type DataStore = Free DataStoreF 

würde Ich mag DataStore eine Instanz von MonadError mit der Fehlermeldung als (Free (Error str)) behandelt machen:

instance MonadError String DataStore where 
    throwError str = errorDS str 
    catchError (Free (ErrorDS str)) f = f str 
    catchError x _ = x 

Aber ich laufe in Overlapping Instanzen Fehler.

Was ist der richtige Weg, um die DataStore Monade und Instanz von MonadError zu machen?

+0

Sie können es in einen 'newtype' verpacken und die Instanzen selbst steuern. Ich glaube nicht, dass es einen saubereren Weg dafür gibt. Die Instanzen von 'FreeT' gehen davon aus, dass Sie keine der von anderen Transformers bereitgestellten mtl-Klassen bereitstellen werden. – Cirdec

+0

Wie unterscheidet sich das von der letzten Zeile des ersten Codeblocks? –

+1

@ JohnF.Miller In Ihrem Beispiel ist 'DataStore' nur ein Typ-Alias, kein' NewType', der Ihnen automatisch alle 'Free'-Instanzen liefert. Wenn Sie es jedoch stattdessen als 'newtype' definieren, können Sie' GeneralisierteNeueTypDerivierung' verwenden, um auszuwählen, welche Instanzen Sie erben möchten, und Sie können Ihre eigene 'MonadError'-Instanz definieren. –

Antwort

2

Ihre Instanz und die Instanz von der Bibliothek gegeben:

instance (Functor m, MonadError e m) => MonadError e (Free m) 

ist in der Tat überlappend, aber das bedeutet nicht, dass sie nicht kompatibel sind. Beachten Sie, dass die obige Instanz in gewissem Sinne "allgemeiner" ist als Ihre - jeder Typ, der Ihrer Instanz entspricht, würde mit dieser übereinstimmen. Wenn man die OverlappingInstances Erweiterung (oder mit modernen GHC, ein {-# OVERLAP{S/PING/PABLE} #-} Pragma) verwendet, können Instanzen sich überlappen, und die spezifischste (mindestens allgemeine) Instanz wird verwendet.

Ohne die Erweiterung, z.B.throwError "x" :: DataStore() gibt den Typ Fehler:

* Overlapping instances for MonadError [Char] (Free DataStoreF) 
    arising from a use of `throwError' 
    Matching instances: 
    instance [safe] (Functor m, MonadError e m) => 
        MonadError e (Free m) 
     -- Defined in `Control.Monad.Free' 
    instance [safe] MonadError String DataStore 

aber mit dem Zusatz eines Pragma

instance {-# OVERLAPS #-} 
    MonadError String DataStore where 

der Ausdruck throwError "x" :: DataStore()noch beide Instanzen Matches, da man aber genauer als die andere (die, die Sie schrieb) es ist ausgewählt:

>throwError "x" :: DataStore() 
Free (Error "x") 
+0

Für zukünftige Leser: Alexis Kings Antwort ist wahrscheinlich die passendere Wahl in einer idealen Welt, und sollte stark in Betracht gezogen werden, bevor eine etwas verdächtige Spracherweiterung wie '{- # OVERLAPS # -}' verwendet wird, aber dies so elegant und schnell angesprochen Ausgabe in einer bereits umfangreichen Codebasis, die ich als meine akzeptierte Antwort gewählt habe. Vielen Dank an beide (Stand: 06.08.2016) von Ihnen, die geantwortet haben. –

5

Der Free Typ bietet bereits eine MonadError Instanz für alle kostenlos Monaden:

instance (Functor m, MonadError e m) => MonadError e (Free m) where { ... } 

Wenn Sie schreiben type DataStore = ... Sie einfach definieren einen Typ alias, die im Grunde eine Art Ebene Makro ist. Alle Verwendungen des Typs DataStore werden durch seine Definition ersetzt. Das bedeutet, dass DataStore Verwendung ist nicht zu unterscheiden von der Verwendung Free DataStoreF direkt, also wenn Sie dies tun:

instance MonadError String DataStore where { ... } 

... Sie dies tatsächlich tun:

instance MonadError String (Free DataStoreF) where { ... } 

... und dass Konflikte mit der Instanz oben definiert ist.

Um dies zu umgehen, sollten Sie einen newtype definieren, um einen völlig neuen Typ zu erzeugen, der eigene Instanzen haben kann, die nicht mit den auf Free definierten Instanzen übereinstimmen. Wenn Sie die GeneralizedNewtypeDeriving Erweiterung verwenden, können Sie eine Menge von den vorformulierten vermeiden, die sonst durch eine separates newtype erforderlich wäre:

{-# LANGUAGE GeneralizedNewtypeDeriving -} 

data DataStoreF next = ... 

newtype DataStore a = DataStore (Free DataStoreF a) 
    deriving (Functor, Applicative, Monad) 

instance MonadError String DataStore where { ... } 

Dies sollte das überlappende Instanz Problem, ohne die Notwendigkeit zu vermeiden, schreiben Sie sich die Functor, Applicative und Monad Instanzen manuell.

+0

Was macht die eingebaute Instanz? Ich habe es angeschaut, aber ich kann die Anforderung nicht erfüllen, dass 'DataStoreF' ein' MonadError' ist, weil es in Wirklichkeit keine Monade ist. –

+0

@ JohnF.Miller Die Standardinstanz sagt nur, dass 'Free' ein [Monad Transformer] ist (https://hackage.haskell.org/package/transformers-0.5.2.0/docs/Control-Monad-Trans-Class.html) das behebt "MonadError". –

+1

@ JohnF.Miller Richtig, es funktioniert nur, wenn 'DataStoreF' bereits eine Monade ist, die den gesamten Zweck des gemeinsamen Anwendungsfalles für' Free' besiegt. Es ist jedoch kein großes Problem, da ein "Newtype" genauso gut funktioniert. –