2015-09-14 19 views
8

Ich versuche, z.B. ExceptT a (StateT A M), für einige Betontypen A und Monad M, und wickeln Sie sie in meine neuen benutzerdefinierten Monaden ein.Aufräumen von Monaden - Anwendung eines Monodentransformators in Newtype-Monade umwandeln

Zuerst habe ich festgestellt, dass StateT A M erscheint oft in anderen Zusammenhängen und so habe ich beschlossen, es wäre am besten, dass allein in einer Monade M1 zu wickeln und dann ExceptT a M1 in M2 wickeln.

Die gewünschte Eigenschaft ist M1 und M2 Instanzen MonadState und die Klasse von M zu machen (kann davon ausgehen, es MyMonadClass genannt wird). Auch M2 sollte eine Instanz von MonadError sein.

Zuerst habe ich angefangen von einfachen Typ Synonyme:

type MyState = StateT A M 
type MyBranch a = ExceptT a MyState 

dann dachte ich, dass ich zum ersten Mal der Instanzdeklarationen skizzieren würde (ohne die Instanz Umsetzung) und das ist, wo ich zum ersten Mal fest. instance MonadState A (MyState) schien nicht die richtige Syntax zu sein. Ich dachte, ich müsste newtype MyState' a = StateT a M und dann type MyState = MyState A erstellen (Lässt keine Spracherweiterungen wo nicht notwendig).

Doch sobald ich die Synonyme in newtype Erklärungen begann Umwandlung begann ich die Verbindung zu den StateT A M und ExceptT ... Arten zu verlieren.

newtype MyState' s a = MyState' { runMyState :: s -> (s, a) } 
type MyState = MyState' A 
newtype MyBranch e a = MyBranch { runMyBranch :: MyState (Either e a) } 

Jetzt sind die Transformatoren, die bereits umgesetzt sind verschwunden, und ich denke, ich versuche, etwas zu tun, das nicht viel Sinn macht. Meine Frage ist also: Wie würde man diese Art von Verhalten korrekt in neue zusammengesetzte Monaden verpacken, die die Schichten darunter zugänglich machen, so dass man unnötiges Heben vermeidet und die Dinge klar und gut organisiert hält.

Antwort

9

Das normale Muster ist, einen neuen Typ für Ihren kompletten Transformatorstapel zu definieren.

data A = A 
data E = E 

newtype MyBranchT m a = MyBranchT { getMyBranchT :: ExceptT E (StateT A m) a } 

Wenn es Teile des Stapels gibt, die eigene sinnvolle neue Fähigkeiten hinzufügen, definieren Sie auch neue Typen für diese Teile.

Dann verwenden Sie GeneralizedNewtypeDeriving, um Instanzen für alle verschiedenen Monad-Klassen zu erhalten.

{-# LANGUAGE GeneralizedNewtypeDeriving #-} 

-- base 
import Control.Applicative 
import Control.Monad 

-- transformers 
import Control.Monad.IO.Class 
import Control.Monad.Trans.Class 
import Control.Monad.Trans.Except 
import Control.Monad.Trans.State.Lazy 

-- mtl classes 
import Control.Monad.Cont.Class 
import Control.Monad.Error.Class 
import Control.Monad.Reader.Class 
import Control.Monad.State.Class 
import Control.Monad.Writer.Class 

data A = A 
data E = E 

newtype MyBranchT m a = MyBranchT { getMyBranchT :: ExceptT E (StateT A m) a } 
    deriving (Functor, Applicative, Monad, MonadIO,  -- classes from base and transformers 
       {- Alternative, MonadPlus, -}    -- if E is a monoid 
       MonadState A, MonadError E,    -- classes from mtl that MyBranchT provides 
       MonadCont, MonadReader r, MonadWriter w) -- classes from mtl that might be available from m 

Sie müssen das MonadTrans Beispiel mit der Hand schreiben. lift ist immer nur lift einmal für jeden der Transformatoren im Stapel.

instance MonadTrans MyBranchT where 
    lift = MyBranchT . lift . lift 

Wenn es irgendwelche Stücke des Stapels, die sinnvolle neue Funktionen X auf ihren eigenen hinzufügen Sie auch eine neue MonadX Klasse für diese Funktionen definieren würde, schreiben MonadX Instanzen für jede der Monade Trafos (StateT, ExceptT , ContT, usw.), wenn möglich, und leiten Sie die MonadX Instanz für Ihren Transformatorstapel (MyBranchT) ab.

Sie werden in der Regel auch eine Art Synonym für MyBranchT Identity und Funktionen runMyBranchT machen und runMyBranch

import Data.Functor.Identity 

type MyBranch a = MyBranchT Identity a 

runMyBranchT :: MyBranchT m a -> A -> m (Either E a, A) 
runMyBranchT mb s = runStateT (runExceptT (getMyBranchT mb)) s 

runMyBranch :: MyBranch a -> A -> (Either E a, A) 
runMyBranch mb s = runIdentity $ runMyBranchT mb s 
+0

Vielen Dank, diese Antwort eine große ist. Kann ich irgendwie auf Spracherweiterungen wie "GeneralizedNewtypeDeriving" verzichten? – jakubdaniel

+0

@JakubDaniel Sicher kannst du alle Instanzen von Hand schreiben. Aber du willst nicht (und wirst wahrscheinlich einen vermasseln), also 'GeneralizedNewtypeDeriving'. – Cirdec

+0

Um den eigentlichen Code zu sehen, der abgeleitet wird, führen Sie den Befehl ': set -ddump-deriv' in GHCi aus, er zeigt Ihnen den ganzen Code, den GHC großzügig für Sie schreibt. 'Daten AB = A | B Ableiten von Eq 'Dumps' Instanz GHC.Classes.Eq Ghci5.AB wobei (GHC.Classes. ==) Ghci5.A Ghci5.A = GHC.Types.True; (GHC.Classes. ==) Ghci5.B Ghci5.B = GHC.Types.Thru; (GHC.Classes. ==) _ _ = GHC.Types.False' –