2013-07-30 14 views
21

Kann es einen mtl-ähnlichen Mechanismus für Monadetransformatoren geben, die von FreeT/ProgramT erstellt wurden?Monad Stack Penetration Klassen mit Free/Operational Monad Transformers?

Mein Verständnis der Geschichte ist wie folgt. Es war einmal Monad Transformator wurde erfunden. Dann begannen die Leute, die Monade-Transformatoren aufeinander zu stapeln, und es war dann lästig, überall die lift einzufügen. Dann haben ein paar Leute Monad Klassen erfunden, so dass wir z. ask :: m r in einer beliebigen Monade m so, dass MonadReader r m. Dies war möglich durch jede Monade Klasse macht eindringen jeden Monade Transformators, wie

(Monoid w, MonadState s m) => MonadState s (WriterT w m)
MonadWriter w m => MonadWriter w (StateT s m)

Sie solches Paar von Instanzdeklarationen müssen für jedes Paar von Monade Transformatoren, so, wenn es n Monade Transformatoren gibt es n^2 Kosten. Dies war jedoch kein großes Problem, da die Leute vordefinierte Monaden verwenden und nur selten eigene erstellen. Die Geschichte soweit ich verstehe, und ist auch ausführlich z. in dem folgenden Q & A:

Avoiding lift with Monad Transformers

Dann ist mein Problem mit den neuen Freien Monaden http://hackage.haskell.org/package/free und Operational Monaden http://hackage.haskell.org/package/operational. Sie erlauben uns, unser eigenes DSL zu schreiben und es als Monaden zu benutzen, indem wir einfach die Sprache als algebraischen data Typ definieren (Operational braucht nicht einmal Functor Instanzen). Eine gute Nachricht ist, dass wir Monaden und Monade-Transformatoren kostenlos haben können; Wie wäre es dann mit Monad-Klassen? Schlechte Nachrichten sind, dass die Annahme "wir definieren selten unsere eigenen Monadetransformatoren" nicht mehr gilt.

Als Versuch, dieses Problem zu verstehen, machte ich zwei ProgramT s und ließ sie einander durchdringen;

https://github.com/nushio3/practice/blob/master/operational/exe-src/test-05.hs

Die operational Paket unterstützen Klassen Monade nicht so nahm ich eine weitere Implementierung minioperational und modifiziert es zu arbeiten, wie ich brauche; https://github.com/nushio3/minioperational

Trotzdem brauchte ich die spezielle Instanz Erklärung

instance (Monad m, Operational ILang m) => Operational ILang (ProgramT SLang m) where

weil die allgemeine Erklärung der folgenden Form führt zu unentscheidbar Instanzen.

instance (Monad m, Operational f m) => Operational f (ProgramT g m) where

Meine Frage ist, wie können wir es leichter, lassen Sie unsere Operational Monaden durchdringen einander machen. Oder, ist mein Wunsch, Penetration für jede operierende Monade zu bekommen, die schlecht gestellt ist.

Ich würde auch die korrekte Fachbegriff für Penetration gerne wissen :)

Antwort

6

ich versucht, ein bisschen anderen Ansatz, der zumindest eine Teilantwort gibt.Da das Stapeln von Monaden manchmal problematisch sein kann und wir wissen, dass alle Monaden aus einem bestimmten Datentyp aufgebaut sind, habe ich stattdessen versucht, die Datentypen zu kombinieren.

Ich fühle mich wohler mit MonadFree, also habe ich es verwendet, aber ich nehme an, ein ähnlicher Ansatz könnte auch für Operational verwendet werden.

Beginnen wir mit der Definition unserer Datentypen starten:

{-# LANGUAGE DeriveFunctor, FlexibleContexts, 
      FlexibleInstances, FunctionalDependencies #-} 
import Control.Monad 
import Control.Monad.Free 

data SLang x = ReadStr (String -> x) | WriteStr String x 
    deriving Functor 
data ILang x = ReadInt (Int -> x) | WriteInt Int x 
    deriving Functor 

Um zwei functors miteinander kombinieren sie in einem freien Monade verwenden, lassen Sie uns ihre coproduct definieren:

data EitherF f g a = LeftF (f a) | RightF (g a) 
    deriving Functor 

Wenn wir Erstellen Sie eine kostenlose Monade über EitherF f g, können wir die Befehle von beiden aufrufen. Um diesen Prozess transparent zu machen, können wir MPTC verwenden Umwandlung zu ermöglichen, von jedem des Funktor in das Ziel ein:

class Lift f g where 
    lift :: f a -> g a 
instance Lift f f where 
    lift = id 

instance Lift f (EitherF f g) where 
    lift = LeftF 
instance Lift g (EitherF f g) where 
    lift = RightF 

jetzt können wir nur lift nennen und wandeln entweder Teil in das Co-Produkt.

Mit einer Hilfsfunktion

wrapLift :: (Functor g, Lift g f, MonadFree f m) => g a -> m a 
wrapLift = wrap . lift . fmap return 

wir generische Funktionen schließlich erstellen können, die es uns ermöglichen, Befehle von etwas nennen wir heben kann in einen Funktor:

readStr :: (Lift SLang f, MonadFree f m) => m String 
readStr = wrapLift $ ReadStr id 

writeStr :: (Lift SLang f, MonadFree f m) => String -> m() 
writeStr x = wrapLift $ WriteStr x() 

readInt :: (Lift ILang f, MonadFree f m) => m Int 
readInt = wrapLift $ ReadInt id 

writeInt :: (Lift ILang f, MonadFree f m) => Int -> m() 
writeInt x = wrapLift $ WriteInt x() 

Dann kann das Programm zum Ausdruck gebracht werden, als

myProgram :: (Lift ILang f, Lift SLang f, MonadFree f m) => m() 
myProgram = do 
    str <- readStr 
    writeStr "Length of that str is" 
    writeInt $ length str 
    n <- readInt 
    writeStr "you wanna have it n times; here we go:" 
    writeStr $ replicate n 'H' 

ohne weitere Instanzen zu definieren.

Während alle oben genannten gut funktioniert, ist das Problem, wie generisch solche zusammengesetzte freie Monaden ausgeführt werden. Ich weiß nicht, ob es überhaupt möglich ist, eine vollständig generische, zusammensetzbare Lösung zu haben.

Wenn wir nur eine Basis Funktors haben, können wir es als

laufen
runSLang :: Free SLang x -> String -> (String, x) 
runSLang = f 
    where 
    f (Pure x)    s = (s, x) 
    f (Free (ReadStr g)) s = f (g s) s 
    f (Free (WriteStr s' x)) _ = f x s' 

Wenn wir zwei haben, müssen wir den Zustand der beiden einzufädeln:

runBoth :: Free (EitherF SLang ILang) a -> String -> Int -> ((String, Int), a) 
runBoth = f 
    where 
    f (Pure x)      s i = ((s, i), x) 
    f (Free (LeftF (ReadStr g)))  s i = f (g s) s i 
    f (Free (LeftF (WriteStr s' x))) _ i = f x s' i 
    f (Free (RightF (ReadInt g)))  s i = f (g i) s i 
    f (Free (RightF (WriteInt i' x))) s _ = f x s i' 

Ich denke, ein Möglichkeit wäre, das Ausführen der Funktoren mit iter :: Functor f => (f a -> a) -> Free f a -> a von free auszudrücken und dann eine ähnliche, kombinierende Funktion

iter2 :: (Functor f, Functor g) 
     => (f a -> a) -> (g a -> a) -> Free (EitherF f g) a -> a 
zu erstellen

Aber ich hatte keine Zeit, es auszuprobieren.

+0

Danke, Petr. Mit Ihrer Hilfe habe ich verstanden, wie man zwei Typkonstruktoren mit Hilfe von »(* -> *)« kombiniert. https://github.com/nushio3/practice/blob/master/operational/exe-src/test-06.hs Composable Dolmetscher schreiben sind genauso einfach: https://github.com/nushio3/practice/ Blob/Master/Betrieb/exe-src/test-07.hs Wir können sogar auf Kosten des 'OverlappingInstances' mehr als zwei Sprachen zusammensetzen. https://github.com/nushio3/practice/blob/master/operational/exe-src/test-08.hs – nushio