2016-09-26 2 views
0

ich eine Klasse habenLösen von Überlappung von Instanzen mit Typ-Familien

class Monad m => MyClass m where 
    type MyType1 m 
    type MyType2 m 
    ... 

    a :: m (MyType1 m) 
    b :: m (MyType2 m) 
    c :: m (MyType3 m) 
    ... 

und ich habe auch Bündel von Instanzen, die die Funktionen (a ...) angemessen

instance MyClass A where 
    type MyType1 A = Int 
    ... 

    a = ... 
    ... 

instance MyClass (B a) where 
    type MyType1 (B a) = Char 
    ... 

    a = ... 
    ... 

... 

implementieren, aber ich habe auch viele Instanzen, die nichts nützliches tun, abgesehen von der Aufhebung der Implementierung durch Transformatoren:

instance MyClass m => MyClass (MyTransA m) 
    type MyType1 (MyTransA m) = MyType1 m 
    ... 

    a = lift a 
    ... 

instance MyClass m => MyClass (MyTransB m) 
    type MyType1 (MyTransB m) = MyType1 m 
    ... 

    a = lift a 
    ... 

... 

und dies erweist sich als viel vorformulierten sein zu schreiben, also wollte ich diese sich wiederholende uninteressant Instanzen mit einfach

class MonadTrans t => AutoLiftMyClass t 

instance (AutoLiftMyClass a, MyClass m) => MyClass (a m) 
    type MyType1 (a m) = MyType1 m 
    ... 

    a = lift a 
    ... 

die mich nur

instance AutoLiftMyClass MyTransA 
instance AutoLiftMyClass MyTransB 
... 

zu schreiben erlauben würde, ersetzen, die Aufhebung zu erhalten kostenlos, a, b, ... für jeden MyTransA, MyTransB ist

das Problem

... die Aufzählung aller aufgehoben zu vermeiden, dass aus irgendeinem Grund (I reall Ich weiß nicht, warum) GHC betrachtet nur die RHS von Instanz Deklarationen, also meine AutoLiftMyClass kollidiert auf Typ Familieninstanzen für MyType1, ... mit allen sinnvollen Instanzen A, B, ... (diejenigen, die keine Instanz von AutoLiftMyClass)

Ich habe einige Beiträge und Artikel im Wiki über Closed Type Familien gesehen, aber sie ergeben für mich nicht viel Sinn. Gibt es eine Möglichkeit, wie diese Idee funktioniert?

+0

[Es gibt ein ähnliches Problem in 'mtl' mit einer Reihe von Instanzen, die nur darauf brennen, zusammen gruppiert zu werden und _exactly_ den gleichen Code zu haben.] (Https://hackage.haskell.org/package/mtl-2.2 .1/docs/src/Control-Monad-State-Class.html # state) Wenn Edward Kmett keinen besseren Weg gefunden hat, dies zu tun, erwarte ich auch nicht, dass ... – Alec

+0

Dies nicht die Adresse Hauptfrage, aber, warum Haskell Compiler nur zum Beispiel Auflösung an der Spitze sehen, Kmett Kommentare über die Open-World-Annahme und Compiler Fortschritt in https://www.reddit.com/r/haskell/comments/4mrgeb/how_do_you_avoid_repeating_mtlstyle_instances sehen/ – hao

Antwort

2

Sie DefaultSignatures verwenden könnte, die genau dieses Problem lösen soll:

class Monad m => MyClass m where 

    type MyType m :: * 
    type MyType m = MyTypeDef m 

    val :: m (MyType m) 
    default val :: (MyClassDef m) => m (MyTypeDef m) 
    val = defVal 

Die Varianten der MyClass sind MyType nur eine Kopie der oben genannten, im Wesentlichen:

class MyClassDef m where 
    type MyTypeDef m :: * 
    defVal :: m (MyTypeDef m) 

instance 
    (MonadTrans t, Monad n, MyClass n 
) => MyClassDef (t (n :: * -> *)) where 
    type MyTypeDef (t n) = MyType n 
    defVal = lift val 

Beachten Sie, dass Eine Instanz wird nur benötigt, um die Übereinstimmung des t n Konstruktors sauber zu gestalten. Überlappungen sind kein Problem, da sie nur in Standard-Signaturen verwendet werden.

Dann sind Ihre Instanzen einfach:

instance (MyClass m) => MyClass (ReaderT r m) 
instance (MyClass m, Monoid r) => MyClass (WriterT r m) 
instance (MyClass m) => MyClass (StateT r m) 

Natürlich kann es wünschenswert sein, mehrere Optionen für eine Standardimplementierung zu haben, aber das ist nicht viel schwieriger als oben - Sie einfach eine andere Art addieren, Klasse:

class MyClassDef (ix :: Symbol) m where 
    type MyTypeDef ix m :: * 
    defVal :: m (MyTypeDef ix m) 

instance 
    (MonadTrans t, Monad n, MyClass n 
) => MyClassDef "Monad Transformer" (t (n :: * -> *)) where 
    type MyTypeDef "Monad Transformer" (t n) = MyType n 
    defVal = lift val 

Beachten sie, dass ix in defVal nicht eindeutig ist, aber ich werde TypeApplications um es zu bekommen zu verwenden. Sie können dasselbe mit Proxy erreichen.

Der zusätzliche Parameter wird bestimmt, wenn Sie die Instanz schreiben, und davon ausgehend, dass Sie keine überlappenden Instanzen verwenden (was Sie nicht tun sollten, wenn Sie eine gute Typ-Inferenz, insbes.wenn Sie Inferenz wollen Typ mit anderen mtl-Stil-Bibliotheken arbeiten) können Sie einfach fügen Sie es als assoziiertes Typ:

class Monad m => MyClass m where 
    type UseDef m :: Symbol 

    type MyType m :: * 
    type MyType m = MyTypeDef (UseDef m) m 

    val :: m (MyType m) 
    default val :: (MyClassDef (UseDef m) m) => m (MyTypeDef (UseDef m) m) 
    val = defVal @(UseDef m) 

Wenn Sie vergessen UseDef zu implementieren, erhalten Sie einen Fehler wie:

* Could not deduce (MyClassDef (UseDef (StateT r m)) (StateT r m)) 
    arising from a use of `Main.$dmval' 

aber Sie können Ihre eigenen Fehler für einen fehlenden Standard zur Verfügung stellen, wenn Sie wollen:

instance (TypeError (Text ("No default selected"))) => MyClassDef "" m 

class Monad m => MyClass m where 
    type UseDef m :: Symbol 
    type UseDef m = "" 

und wenn Sie alle Methoden und Typen implementieren, erhalten Sie keinen Fehler, als UseDef wird nirgendwo verwendet - nur in einer instanziierten Standardsignatur, die nicht einmal existiert, wenn die Implementierung gegeben wird.

Ihre Instanzen entstehen die Kosten für eine zusätzliche Zeile von vorformulierten, aber es ist nicht viel (vor allem mit Copy-Paste.):

instance (MyClass m) => MyClass (ReaderT r m) where 
    type UseDef (ReaderT r m) = "Monad Transformer" 

instance (MyClass m, Monoid r) => MyClass (WriterT r m) where 
    type UseDef (WriterT r m) = "Monad Transformer" 

instance (MyClass m) => MyClass (StateT r m) where 
    type UseDef (StateT r m) = "Monad Transformer" 

Beachten Sie, dass Sie tun, um die erforderlichen Kontexte für jeden liefern Beispiel.


Beachten Sie, dass all dies wirklich notwendig ist, wenn Sie nur überlappende Instanzen vermeiden möchten. Wenn Sie dies nicht tun, dann die einfache Lösung verwenden und nur

instance {-# OVERLAPS #-} (AutoLiftMyClass a, MyClass m) => MyClass (a m) 

schreiben oder schalten Sie OverlappingInstances.

+0

Danke, das sieht aus, als könnte es tun, was ich brauche, außer 'MyClass' definiert bereits einige Standardimplementierungen verschiedener Funktionen (die ich vergessen zu erwähnen), ich bin mir nicht sicher, wie ich diese beibehalten kann ... während ich noch Standardwerte für die Standard-SI gnature – jakubdaniel

+0

Auch ich denke, es löst mein Problem nicht, ich hätte es jedoch klarer gemacht. Einige meiner "vernünftigen Instanzen" sind auch Transformatoren, die ihre eigenen "MyType" -Synonyme definieren (wie zum Beispiel "type MyType1 (MyCoolTrans m) = [MyType1 m]") ... mit dieser Methode laufe ich auf 'Could not match type 'MyType1 m' mit '[MyType1 m]' 'mit der (nicht standardmäßigen) Instanz für' MyCoolTrans m' – jakubdaniel

+0

Setze einen Standardwert für 'UseDef', der den 'default'-Standard (sozusagen) auswählt und einen Constraint wie folgt setzt 'UseDef m ~" .. "' in den vorhandenen Standardeinstellungen. (Es sei denn, Sie meinen Standard-Funktionsimplementierungen, aber keine Standard-Signaturen - in diesem Fall müssen Sie nichts Besonderes tun, es wird einfach funktionieren). Es gibt nicht annähernd genug Informationen, um zu sagen, was den Fehler verursacht, aber es ist nicht inhärent für das Design, einfach ein Programmierfehler - es gibt nichts über diesen Ansatz, der Sie daran hindern würde, solch eine Instanz zu deklarieren. – user2407038

Verwandte Themen