¡Gran pregunta!
Un transformador de mónada es un tipo que agrega cierta funcionalidad a una base de mónada arbitraria, al tiempo que preserva la mónada. Lamentablemente, los transformadores de mónada son inexpresables en C# porque hacen un uso esencial de los tipos de mayor nivel. Por lo tanto, trabajando en Haskell,
class MonadTrans (t :: (* -> *) -> (* -> *)) where
lift :: Monad m => m a -> t m a
transform :: Monad m :- Monad (t m)
Repasemos esta línea por línea. La primera línea declara que un transformador de mónada es un tipo t
, que toma un argumento del tipo * -> *
(es decir, un tipo que espera un argumento) y lo convierte en otro tipo de tipo * -> *
. Cuando te das cuenta de que todas las mónadas tienen el tipo * -> *
, puedes ver que la intención es que t
convierta las mónadas en otras mónadas.
La siguiente línea dice que todos los transformadores monad deben apoyar una operación lift
, que tiene una mónada arbitraria m
y ascensores en el mundo del transformador t m
.
Finalmente, el método transform
dice que para cualquier mónada m
, t m
también debe ser una mónada. Estoy utilizando el vinculación operador :-
de the constraints
package.
Esto tendrá más sentido con un ejemplo. Aquí hay un transformador de mónada que agrega Maybe
-ness a una base de mónada arbitraria m
. El operador nothing
nos permite abortar el cálculo.
newtype MaybeT m a = MaybeT { runMaybeT :: m (Maybe a) }
nothing :: Monad m => MaybeT m a
nothing = MaybeT (return Nothing)
Para que MaybeT
a ser un transformador mónada, tiene que ser una mónada siempre que su argumento es una mónada.
instance Monad m => Monad (MaybeT m) where
return = MaybeT . return . Just
MaybeT m >>= f = MaybeT $ m >>= maybe (return Nothing) (runMaybeT . f)
Ahora para escribir la implementación MonadTrans
. La implementación de lift
envuelve el valor de retorno de la mónada base en Just
.La implementación de transform
no es interesante; simplemente le dice al solucionador de restricciones de GHC que verifique que MaybeT
es de hecho una mónada siempre que su argumento sea.
instance MonadTrans MaybeT where
lift = MaybeT . fmap Just
transform = Sub Dict
Ahora podemos escribir un cálculo que utiliza monádico MaybeT
añadir la falta de, por ejemplo, la mónada State
. lift
nos permite utilizar los métodos State
estándar get
y put
, pero también tenemos acceso a nothing
si necesitamos fallar el cálculo. (Pensé en usar el ejemplo de IEnumerable
(también conocido como []
), pero hay algo de perverso en la adición de la falta de una mónada que ya lo soporta.)
example :: MaybeT (State Int)()
example = do
x <- lift get
if x < 0
then nothing
else lift $ put (x - 1)
Lo que hace que los transformadores monad realmente útil es su capacidad de apilamiento. Esto le permite componer grandes mónadas con muchas capacidades de muchas pequeñas mónadas con una capacidad cada una. Por ejemplo, una aplicación determinada puede necesitar IO, leer variables de configuración y lanzar excepciones; esto se codifica con un tipo como
type Application = ExceptT AppError (ReaderT AppConfig IO)
Existen herramientas en the mtl
package que ayudan a abstraer sobre la recogida y el orden de los transformadores monad precisa en una pila dada, lo que le permite eludir las llamadas a lift
.
Encontré una referencia muy interesante de la combinación de mónadas: http://heinrichapfelmus.github.com/operational/Documentation.html#alternatives-to-monad-transformers Defiende una forma general de combinar mónadas. También debería leer este documento, que describe cómo combinar tipos de datos de forma general y extensible: http://www.cs.ru.nl/~wouters/Publications/DataTypesALaCarte.pdf –