2013-07-30 16 views
21

Può esserci un meccanismo simile a mtl per i trasformatori monad creati da FreeT/ProgramT?Classi di penetrazione dello stack di Monad con trasformatori Monad liberi/operativi?

La mia comprensione della storia è la seguente. C'era una volta inventato il trasformatore di monad. Poi la gente ha iniziato a impilare i trasformatori monad uno sull'altro, poi ha trovato fastidioso inserire lift ovunque. Poi un paio di persone hanno inventato le classi monad, così che possiamo ad es. ask :: m r in qualsiasi monade m tale che MonadReader r m. Questo è stato possibile realizzando ogni classe monade penetrare ogni trasformatore monade, come

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

Occorrono tale coppia di dichiarazioni istanza per ogni coppia di trasformatori monade, quindi quando c'è n monad trasformatori c'è n^2 costi. Questo non era un grosso problema, tuttavia, poiché le persone utilizzano principalmente le monade predefinite e raramente creano le proprie. La storia fino ad ora è comprensibile, ed è anche dettagliata ad es. nel seguente Q & A:

Avoiding lift with Monad Transformers

Allora il mio problema è con le nuove monadi gratuiti http://hackage.haskell.org/package/free e monadi operativi http://hackage.haskell.org/package/operational. Ci permettono di scrivere la nostra DSL e usarla come monade, semplicemente definendo il linguaggio come un tipo algebrico data (Operational non ha nemmeno bisogno delle istanze Functor). La buona notizia è che possiamo avere monadi e trasformatori monad gratis; allora che ne dici delle lezioni di monad? La cattiva notizia è che l'ipotesi "raramente definiamo i nostri trasformatori monadi" non regge più.

Come tentativo di comprendere questo problema, ho creato due ProgramT s e li ho fatti penetrare l'un l'altro;

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

Il pacchetto operational non supporta le classi monade così ho preso un'altra implementazione minioperational e modificato a lavorare come ho bisogno; https://github.com/nushio3/minioperational

Eppure, mi serviva la dichiarazione di istanza specializzata

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

perché la dichiarazione generale del seguente modulo conduce alle istanze indecidibili.

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

La mia domanda è che come possiamo rendere più facile da affittare I nostri monadi operativi penetrano reciprocamente. Oppure, è il mio desiderio di avere una penetrazione per qualsiasi monade operativa mal posta.

mi piacerebbe anche sapere il corretto termine tecnico per penetrazione :)

risposta

6

ho provato un approccio diverso po ', che dà almeno una risposta parziale.Dal momento che impilare le monadi può a volte essere problematico, e sappiamo che tutte le nostre monadi sono costruite da un certo tipo di dati, ho provato invece a combinare i tipi di dati.

Mi sento più a mio agio con MonadFree così l'ho usato, ma suppongo che un approccio simile potrebbe essere utilizzato anche per Operational.

Cominciamo con la definizione dei nostri tipi di dati:

{-# 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 

Al fine di combinare due funtori insieme per il loro utilizzo in una monade libera, definiamo la loro coprodotto:

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

Se noi creare una monade gratuita su EitherF f g, possiamo chiamare i comandi da entrambi. Al fine di rendere questo processo trasparente, possiamo usare MPTC per consentire la conversione da ciascuna delle funtore nel bersaglio uno:

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 

ora possiamo solo chiamare lift e convertire o parte nella coprodotto.

Con una funzione di supporto

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

possiamo finalmente creare funzioni generiche che ci permettono di chiamare i comandi da tutto ciò che può sollevare in un funtore:

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() 

Quindi, il programma può essere espresso come

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' 

senza ulteriori istanze.


Mentre tutto quanto sopra funziona bene, il problema è come generare genericamente tali monade libere composte. Non so se sia persino possibile avere una soluzione compatta e generica.

Se abbiamo solo un funtore di base, siamo in grado di eseguirlo come

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' 

Se abbiamo due, abbiamo bisogno di infilare lo stato di ciascuno di essi:

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' 

immagino uno la possibilità sarebbe quella di esprimere i funtori usando iter :: Functor f => (f a -> a) -> Free f a -> a da free e quindi creare una funzione combinata simile

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

Ma non ho avuto il tempo di provarlo.

+0

Grazie, Petr. Con il tuo aiuto ho capito come combinare due costruttori di tipi usando Either su '(* -> *)'. https://github.com/nushio3/practice/blob/master/operational/exe-src/test-06.hs La scrittura di interpreti componibili è altrettanto semplice: https://github.com/nushio3/practice/ blob/master/operational/exe-src/test-07.hs Possiamo anche comporre più di due lingue al costo di 'OverlappingInstances'. https://github.com/nushio3/practice/blob/master/operational/exe-src/test-08.hs – nushio