haskell monad-transformers operational free-monad

haskell - Monad Stack Penetration Classes con transformadores de mónada gratuitos/operativos?



monad-transformers operational (1)

¿Puede haber un mecanismo tipo mtl para transformadores de mónada creados por FreeT / ProgramT?

Mi comprensión de la historia es la siguiente. Érase una vez el transformador de mónada fue inventado. Luego la gente comenzó a apilar los transformadores de mónada uno sobre otro, y luego encontró molesto insertar la lift todas partes. Luego, un par de personas inventaron las clases de mónadas, de modo que, por ejemplo, podemos ask :: mr en cualquier mónada m tal que MonadReader rm . Esto fue posible haciendo que cada clase de mónada penetre en cada mónada transformador, como

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

necesita tal par de declaraciones de instancia para cada par de transformadores de mónada, por lo que cuando hay n transformadores de mónada hay n ^ 2 costos. Sin embargo, esto no fue un gran problema, ya que la mayoría de las personas usarán mónadas predefinidas y rara vez crearán las suyas propias. La historia hasta ahora lo entiendo, y también se detalla, por ejemplo, en las siguientes preguntas y respuestas:

Evitando levantar con Monad Transformers

Entonces, mi problema es con las nuevas mónadas gratuitas http://hackage.haskell.org/package/free y las mónadas operativas http://hackage.haskell.org/package/operational . Nos permiten escribir nuestro propio DSL y usarlo como mónadas, solo definiendo el lenguaje como un tipo de data algebraico (Operational ni siquiera necesita instancias de Functor ). La buena noticia es que podemos tener mónadas y transformadores de mónada gratis; entonces ¿qué hay de las clases de mónada? La mala noticia es que la suposición de que "raramente definimos nuestros propios transformadores de mónada" ya no se cumple.

Como un intento de entender este problema, hice dos ProgramT y los hice penetrar entre sí;

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

El paquete operational no admite clases de mónadas, así que tomé otra minioperational implementación y la modifiqué para que funcionara como necesito; https://github.com/nushio3/minioperational

Aún así, necesitaba la declaración de instancia especializada.

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

Porque la declaración general del siguiente formulario conduce a instancias indecibles.

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

Mi pregunta es cómo podemos facilitar que nuestras mónadas operativas penetren entre sí. O bien, es mi deseo tener penetración para cualquier mónada operacional mal planteada.

También me gustaría saber el término técnico correcto para penetración :)


Intenté un enfoque un poco diferente, que da al menos una respuesta parcial. Dado que apilar las mónadas a veces puede ser problemático, y sabemos que todas nuestras mónadas se construyen a partir de algún tipo de datos, en su lugar intenté combinar los tipos de datos.

Me siento más cómodo con MonadFree así que lo usé, pero supongo que también podría usarse un enfoque similar para Operational .

Comencemos con la definición de nuestros tipos de datos:

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

Para combinar dos funtores juntos para usarlos en una mónada libre, definamos su coproducto:

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

Si creamos una mónada gratuita sobre EitherF fg , podemos llamar a los comandos desde ambos. Para hacer este proceso transparente, podemos usar MPTC para permitir la conversión de cada uno de los funtores al objetivo:

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

Ahora solo podemos llamar a lift y convertir cualquiera de las partes en el coproducto.

Con una función auxiliar.

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

finalmente podemos crear funciones genéricas que nos permiten llamar comandos desde cualquier cosa que podamos elevar a un funtor:

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

Entonces, el programa se puede expresar como

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''

Sin definir ningún caso adicional.

Si bien todo lo anterior funciona bien, el problema es cómo ejecutar genéricamente tales mónadas libres compuestas. No sé si es posible, tener una solución completamente genérica y composable.

Si solo tenemos un funtor base, podemos ejecutarlo como

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''

Si tenemos dos, necesitamos unir el estado de ambos:

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''

Supongo que una posibilidad sería expresar el funcionamiento de los funtores utilizando iter :: Functor f => (fa -> a) -> Free fa -> a from free y luego crear una función de combinación similar

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

Pero no he tenido tiempo de probarlo.