Mónada (programación funcional)En la programación funcional , una mónada (monad en inglés), es un patrón de diseño que representa cálculos definidos como una secuencia de pasos, permitiendo componer funciones con tipos incompatibles encapsulándolos en un tipo monádico. Un tipo de dato con una estructura mónada define lo que simboliza en un bloque de código, o anida funciones del mismo tipo. Esto le permite al programador crear tuberías informáticas que procesan datos en pasos, a los cuales se les asocia un decorador con reglas de proceso adicionales provistas por la mónada.[1] Por lo tanto, las mónadas han sido descritas como un “punto y coma programable”; un punto y coma, siendo el operador usado para unir varias declaraciones en la programación imperativa,[1] en consecuencia la expresión implica que código extra será ejecutado entre las declaraciones de una tubería. Las mónadas también han sido explicadas con un metáfora física, donde se comportan como una línea de ensamblaje, en las que un objeto transporta datos entre unidades funcionales que la van transformando un paso a la vez.[2] También se las puede ver como un patrón de diseño funcional para construir tipos genéricos.[3] Los programas puramente funcionales pueden usar las mónadas para estructurar procedimientos que incluyan operaciones en secuencia como aquellos encontrados en la programación estructurada.[4][5] Muchos conceptos de programación comunes pueden ser descritos en términos de estructuras de mónadas, incluyendo los efectos secundarios, la Entrada/salida, asignación de variables, el manejo de excepciones, el analizador sintáctico, la programación no determinística, concurrencia y continuaciones. Esto permite que tales conceptos sean definidos en maneras puramente funcionales sin el uso de extensiones a la semántica del lenguaje. Los lenguajes como Haskell proveen mónadas en el núcleo estándar, permitiendo a los programadores reutilizar largas partes de su definición formal y aplicar en diversas librerías las mismas interfaces para combinar funciones.[6] Formalmente , una mónada consiste de un constructor de tipo M y dos operaciones , unir y retorno (donde retorno es a veces conocido como unidad). Las operaciones deben cumplir con ciertas propiedades para permitir la composición correcta de funciones mónadicas (es decir, funciones que usen valores de la mónada como sus argumentos o valores de retorno). La operación retorno toma una valor de un tipo plano y lo pone en un contenedor mónadico usando el constructor, creando así un valor mónadico. La operación unir realiza el proceso inverso, extrayendo el valor original del contenedor y pasándolo a la siguiente función asociada en la tubería, posiblemente con chequeos adicionales y transformaciones. Dado que una mónada puede insertar operaciones adicionales en el dominio lógico del programa, se les puede considerar como un tipo de programación orientada a aspectos.[7] La lógica de negocio puede ser definida por la programación de aplicaciones en la tubería, mientras que las operaciones a ser repetidas muchas veces pueden ser operadas por una mónada predefinida construida previamente . El nombre y concepto viene del epónimo (mónada) en la teoría de categorías, donde las mónadas son un tipo de funtor, o sea un mapeo entre categorías; aunque el término mónada en el contexto de la programación funcional es usualmente tomado como correspondiente al término mónada fuerte en la teoría de categorías.[8] HistoriaEl concepto de mónada en la programación apareció en los '80 en el lenguaje Opal, aunque se le llamaban “comandos” y nunca fueron formalmente especificados. Eugenio Moggi fue el que por primera vez describió el uso general de las mónadas para estructurar programas en 1991.[8] Mucha gente continuó su trabajo, incluyendo investigadores de lenguajes de programación como Philip Wadler y Simon Peyton Jones (los cuales estuvieron involucrados en la especificación de Haskell) Versiones tempranas de Haskell usaron una problemática “lista perezosa” para I/O, y Haskell 1.3 introdujo mónadas de una manera más flexible al combinar entrada/salida con evaluación perezosa. En adición a la entrada/salida, los investigadores de los lenguajes de programación y diseñadores de librerías de Haskell han exitosamente aplicado las mónadas a evaluadores sintácticos y a intérpretes de lenguajes de programación. El concepto de las mónadas junto con el de la notación do de Haskell ha sido generalizada para formar funtores aplicativos y flechas. Por un largo tiempo, Haskell y sus derivados han sido los únicos usuarios mayores de mónadas en la programación. También existen formulaciones en Scheme, Perl, Python, Racket, Clojure y Scala, donde las mónadas se han presentado como una opción de diseño de un nuevo lenguaje de programación. Recientemente F# ha incluido la característica de contener expresiones de cómputo o workflows, los cuales son un intento por introducir las construcciones monádicas dentro de una sintaxis más palpable a los programadores con una experiencia en los lenguajes imperativos.[9] Ejemplos motivantesEl lenguaje de programación Haskell es un lenguaje funcional que hace un uso intensivo de mónadas e incluye azúcar sintáctico para hacer la composición de mónadas más conveniente. Todos los ejemplos de código escritos aquí son escritos en Haskell, a menos que se indique lo contrario. Se demuestran dos ejemplos comunes en la introducción a las mónadas: la mónada Maybe y la mónada IO. Las mónadas están desde luego, no recluidas al lenguaje Haskell, el segundo ejemplo muestra la mónada Writer en JavaScript. La mónada MaybeConsidérese la opción de tipo Maybe a (“tal vez un”), representando un valor que es o un valor del tipo a , o ninguno. Para distinguirlos, se tienen dos constructores de tipo de dato algebraico: data Maybe t = Just t | Nothing
Nos gustaría poder usar este tipo como una simple excepción chequeada: en cualquier punto del cómputo, este puede fallar, lo que causa que el resto del proceso sea saltado y que el resultado final sea En el siguiente ejemplo, suma :: Maybe Int -> Maybe Int -> Maybe Int
suma mx my =
case mx of
Nothing -> Nothing
Just x -> case my of
Nothing -> Nothing
Just y -> Just (x + y)
Para aliviar esto, podemos definir operaciones para encadenar tales cómputos de manera conjunta. El operador binario bind (“unir”) ( (>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b
Nothing >>= _ = Nothing -- Un cómputo fallido regresa Nada
(Just x) >>= f = f x -- Aplica la función f al valor x
Usando un poco de azúcar sintáctico, conocida como notación do, el ejemplo puede ser escrito como: suma :: Maybe Int -> Maybe Int -> Maybe Int
suma mx my = do
x <- mx
y <- my
return (x + y)
Dado que este tipo de operación es muy común, existe una función estándar en Haskell ( add :: Maybe Int -> Maybe Int -> Maybe Int
add = liftM2 (+)
La mónada escritoraLa mónada escritora permite que un proceso lleve información adicional “al lado” de su valor de cómputo. Esto puede ser útil para el registro de errores o el debugging de la información que no es el resultado primario.[10] El siguiente ejemplo implementa la mónada escrita en JavaScript: Primero, una mónada escritora es definida. La función unidad crea un nuevo tipo de valor de un tipo básico, el cual lleva una cadena de registro; y bind aplica la función a un valor viejo, y finalmente regresa el nuevo valor de resultado con un registro expandido. Los corchetes trabajan aquí como el constructor de tipo de la mónada, creando un valor de tipo mónadico para la mónada escritora, hecha de componentes más simples (el valor en la posición 0 del arreglo, y la cadena de registro en la posición 1). var unit = function (value) { return [value, ''] };
var bind = function (valorMonadico, transformaConRegistro) {
var value = valorMonadico[0],
registro = valorMonadico[1],
result = transformaConRegistro(value);
return [result[0], registro + result[1]];
};
Tubería es una función auxiliar que concatena una secuencia de binds aplicados a un arreglo de funciones. var pipeline = function (monadicValue, functions) {
for (var key in functions) {
monadicValue = bind(monadicValue, functions[key]);
}
return monadicValue;
};
Ejemplos de funciones que regresen valores del tipo esperados por la mónada escritora de arriba: var squared = function (x) {
return [x * x, 'al cuadrado.'];
};
var halved = function (x) {
return [x / 2, 'a la mitad.'];
};
Finalmente, un ejemplo del uso de la mónada para crear una tubería de funciones matemáticas, con información de debug al lado. pipeline(unit(4), [squared, halved]); // [8, "al cuadrado.a la mitad"]
La mónada IOEn un lenguaje puramente funcional, como Haskell, las funciones no pueden tener efectos externos visibles como parte de la semántica de la función. Aunque una función no puede directamente causar un efecto, puede construir un valor describiendo un efecto deseado, que el llamante puede aplicar a un tiempo conveniente. .[11] En la notación Haskell, un valor de tipo IO a representa una acción que, a la hora de ser realizada , produce un valor de tipo a'. Podemos pensar en un valor de tipo doesFileExist :: FilePath -> IO Bool
removeFile :: FilePath -> IO ()
Así que uno puede pensar Nos gustaría ser capaces de describir todos los tipos básicos de operaciones I/O, por ejemplo, escribir texto a una salida estándar, leer de una entrada estándar, leer y editar archivos, mandar datos sobre redes, etc. En adición, necesitamos ser capaces de componer estas primitivas para formar programas más grandes. Por ejemplo, nos gustaría escribir : main :: IO ()
main = do
putStrLn "Cual es tu nombre?"
name <- getLine
putStrLn ("Gusto en conocerte, " ++ name ++ "!")
¿Cómo formalizar esta notación intuitiva? Para hacerlo, necesitamos ser capaces de realizar operaciones básicas de I/O:
No es necesariamente obvio que las tres operaciones precedentes, junto con set de operaciones I/O nos ayudan a definir cualquier acción de programa, incluyendo las transformaciones de datos, control de flujo y control de flujo recursivo. Podemos escribir el ejemplo de arriba como una sola expresión. main =
putStrLn "Cual es tu nombre?" >>
getLine >>= \name ->
putStrLn ("Gusto en conocerte " ++ name ++ "!")
La estructura de tubería del operador bind se asegura de que 'getLine y putStrLn sean evaluadas una sola vez y en el orden requerido, para que así los efectos de extraer el texto de la entrada y escribir a la salida sean correctamente ejecutadas en la tubería funcional. Esto permanece cierto aun si el lenguaje realiza evaluación de funciones fuera de orden o una evaluación perezosa. Claramente , existe una estructura común entre las definiciones I/O y las Maybe, aun si son diferentes en muchas maneras. Las mónadas son una abstracción de las estructuras antes descritas, de estructuras similares y de sus semejanzas. El concepto general de mónadas incluye una situación donde el programador quiere llevar a cabo un cómputo puramente funcional mientras que un cómputo relacionado se lleva a cabo al lado. Definición FormalUna mónada es una construcción que, dada un tipo de sistema, embebe uno correspondiente (llamo el tipo de sistema mónadico). Tal tipo de sistema mónadico preserva todos los aspectos significativos del tipo de sistema subyacente, mientras que también le añade características particulares de la mónada.[note 1] La formulación usual de una mónada para programación es conocida como una Kleisli triple, y tiene los siguientes componentes:
Si M es el nombre de la mónada y t es un tipo de dato, entonces M t' es el correspondiente tipo en la mónada.
Dado un constructor de tipo M, en la mayoría de los contextos, un valor de tipo M a puede ser pensado como una acción que regresa un valor de tipo a. La operación de retorno toma un valor del tipo plano a y lo pone en un contenedor mónadico de tipo M a; la operación union encadena el valor mónadico de tipo 'M a con una función de tipo a → M b para crear un valor mónadico de tipo M b. Leyes monádicasPara que una mónada se comporte de la manera correcta, sus definiciones deben seguir ciertos axiomas, los cuales en su conjunto son llamados leyes mónadicas.[12] El símbolo ≡ indica equivalencia entre dos expresiones de Haskell en el siguiente texto.
Los axiomas también pueden ser mostrados usando las expresiones en la notación-do:
o usando el operador monádico de composición, (f >=> g) x = (f x) >>= g
:
fmap y joinAunque Haskell define a las mónadas en términos de las funciones return y union, también es posible definir una mónada en términos de retorno y otras dos operaciones, join (juntar) y fmap. Esta formulación se encuentra más apegada con la definición de mónadas en la teoría de categorías. La operación fmap, con tipo (t→u) → M t→M u,[13] toma una función entre 2 tipos y produce una función que hace la misma cosa a los valores en la mónada. La operación join, con tipo M (M t)→M t, aplana dos capas de información mónadica en una sola. Las dos formulaciones son mostradas: fmap f m = m >>= (return . f)
join n = n >>= id
m >>= g ≡ join (fmap g m)
Aquí, m tiene el tipo M t,n tiene el tipo M ( M r), f tiene el tipo t → u, y g tiene el tipo t → M v, donde t, r, u y v son tipos subyacentes. La función retorno caracteriza los funtores apuntados en la misma categoría, usando para esto la habilidad de cargar valores en el funtor. Se debe satisfacer la siguiente ley: return . f ≡ fmap f . return
En adición , la función join caracteriza las mónadas: join . fmap join = join . join
join . fmap return = join . return = id
join . fmap (fmap f) = fmap f . join
Mónadas aditivasUna mónada aditiva es una mónada provista con un cero mónadico mzero y un operador binario mplus que satisface las leyes mónadicas, usando el cero mónadico como unidad. El operador mplus tiene el tipo M t → M t → M t (donde M es el constructor mónadico y t es el tipo de dato subyacente), satisface la ley asociativa y tiene el cero como identidad en la izquierda y la derecha. (Por lo tanto, una mónada aditiva es también un monoide). El unir el mzero con cualquier función produce el cero para el tipo de resultado, justo como el multiplicar 0 por cualquier número da 0. mzero >>= f ≡ mzero
Similarmente, unir cualquier m con una función que siempre devuelve un 0, resulta en un 0. m >>= (\x -> mzero) ≡ mzero
Intuitivamente, el cero representa un valor en la mónada que tiene una estructura exclusivamente relacionada con la mónada y que no tiene valores de tipo subyacente. En la mónada Maybe, Nothing (Nada) es un cero. En la mónada Lista, [ ] (la lista vacía) es también un cero. Azúcar sintáctico: notación doAunque hay ocasiones en las que tiene sentido usar el operador union a = do x <- [3..4]
[1..2]
return (x, 42)
es transformado durante la compilación a: a = [3..4] >>= (\x -> [1..2] >>= (\_ -> return (x, 42)))
Es útil ver la implementación de la mónada lista. Y saber que concatMap mapea una función sobre una lista y concatena (aplana) las listas resultantes: instance Monad [] where
m >>= f = concat (map f m)
return x = [x]
fail s = []
Por lo tanto, las siguientes transformaciones se mantienen y todas las expresiones siguientes son equivalentes: a = [3..4] >>= (\x -> [1..2] >>= (\_ -> return (x, 42)))
a = [3..4] >>= (\x -> concatMap (\_ -> return (x, 42)) [1..2] )
a = [3..4] >>= (\x -> [(x,42),(x,42)] )
a = concatMap (\x -> [(x,42),(x,42)] ) [3..4]
a = [(3,42),(3,42),(4,42),(4,42)]
Nótese que la lista x // y = do
a <- x -- Extraer los valores de x e y , si es que tienen
b <- y
if b == 0 then Nothing else Just (a / b)
x // y = x >>= (\a -> y >>= (\b -> if b == 0 then Nothing else Just (a / b)))
Un ejemplo similar en F# usando una expresión de computación: let readNum () =
let s = Console.ReadLine()
let succ, v = Int32.TryParse(s)
if (succ) then Some(v) else None
let secure_div =
maybe {
let! x = readNum()
let! y = readNum()
if (y = 0)
then None
else return (x / y)
}
El azúcar sintáctica del bloque maybe sería traducido internamente a la siguiente expresión: maybe.Delay(fun () ->
maybe.Bind(readNum(), fun x ->
maybe.Bind(readNum(), fun y ->
if (y = 0) then None else maybe.Return(x / y))))
Funciones mónadicas genéricasDados los valores producidos por la división segura, podríamos querer seguir haciendo cálculos sin tener que checar manualmente si son Nothing (es decir el resultado de intentar hace runa división por 0). Podemos hacer lo anterior usando una función levantante , la cual podemos definir no solo para la mónada Maybe sino también para mónadas arbitrarias. En Haskell esto es llamado liftM2: liftM2 :: Monad m => (a -> b -> c) -> m a -> m b -> m c
liftM2 op mx my = do
x <- mx
y <- my
return (op x y)
Recuerde que las flechas en un tipo se asocian a la derecha, así que liftM2 es una función que toma una función binaria como argumento y regresa otra función binaria. El tipo signature dice: Si m es una mónada, podemos levantar cualquier función binaria por encima de ella. Por ejemplo: (.*.) :: (Monad m, Num a) => m a -> m a -> m a
x .*. y = liftM2 (*) x y
define un operador (.*.) el cual multiplica dos números, a menos que uno de ellos sea Nothing (en cuyo caso regresa Nothing). La ventaja aquí es que no necesitamos adentrarnos en los detalles de la implementación de la mónada; si necesitamos hacer lo mismo con otra función, o en otra mónada, el usar liftM2 hace inmediatamente claro lo que se intenta hacer. Matemáticamente, el operador liftM2 está definido por: Otros ejemplosMónada identidadLa mónada más simple es la mónada identidad, la cual no añade información a los valores. Id t = t
return x = x
x >>= f = f x
Un bloque-do en esta mónada realiza substitución de variables; do {x <- 2; return 3*x} resulta en 6. Desde el punto de vista de la teoría de categorías, la mónada identidad deriva de la unión entre funtores identidad. ColeccionesAlgunos tipos familiares de colección, incluyendo las listas, los sets y multisets, son mónadas. La definición para las listas están dadas aquí. -- "return" construye una lista de un elemento
return x = [x]
-- "bind" concatena las listas obtenidas al aplicar f a cada elemento en la lista xs
xs >>= f = concat (map f xs)
-- El objeto cero es una lista vacía
mzero = []
Las listas de comprensión, son un aplicación especial de la mónada lista. Por ejemplo, la lista comprensión [ 2*x | x <- [1..n], isOkay x] corresponde al cómputo en la mónada lista do {x <- [1..n]; if isOkay x then return (2*x) else mzero;}. La notación de listas por comprensión es similar a la notación de constructor de conjunto, pero un conjunto no puede ser convertido en una mónada, ya que hay una restricción en el tipo de cómputo a ser comparable por igualdad, mientras que una mónada no pone ninguna restricción en los tipos de cómputo. De hecho, el conjunto es una mónada restringida.[14] Las mónadas de colecciones representan naturalmente un algoritmo no determinista. La lista (u otra colección) representa todos los posibles resultados de diferentes caminos no determinísticos de cómputo en ese momento. Por ejemplo, cuando uno ejecuta x <- [1,2,3,4,5], uno dice que la variable x puede de manera no determinística tomar cualquiera de los valores de la lista. Si uno regresara x se evaluara con una lista de resultados de cada camino de cómputo. Nótese que el operador unión de arriba sigue tal tema al realizar f en cada uno de los resultados posibles y después concatena las listas resultantes. Las declaraciones como En un lenguaje con evaluación perezosa, como Haskell, una lista es evaluada solo en el grado en que sus elementos son requeridos: por ejemplo, si uno pide el primer elemento de una lista, solo el primer elemento será computado. Con respecto al uso de la mónada lista para cómputos no determinísticos, esto significa que podemos generar no determinísticamente una lista perezosa de todos los resultados del cómputo, después pedir el primero de ellos, y solo se realizará tanto trabajo sea necesario para obtener el primer resultado. El proceso vagamente corresponde a regresarse: un camino es escogido, después falla en cierto punto (si es que se evalúa Desde el punto de vista de la teoría de las categorías, las mónadas colección son derivadas una unión de funtores entre uno libre y uno subyacente entre la categoría de conjuntos y la categoría de magma (categoría). Tomando diferentes tipos de magmas, obtenemos diferentes tipos de colecciones, como se muestra en la tabla de abajo:
Mónadas estadoUna mónada estado le permite a un programador añadir información de estado de cualquier tipo de cálculo. Dado cualquier tipo de valor, el tipo correspondiente en la mónada estado es una función la cual acepta estado y después regresa un nuevo estado (de tipo s) junto con un valor de retorno (de tipo t). type State s t = s -> (t, s)
Nótese que en esta mónada, en comparación con las ya vistas, toma un parámetro tipo, o sea el estado de tipo de la información. Las operaciones de la mónada se definen de la siguiente manera: -- "return" produce el valor dado sin cambiar el estado.
return x = \s -> (x, s)
-- "bind" modifica m para que aplique a f su resultado
m >>= f = \r -> let (x, s) = m r in (f x) s
Operaciones útiles de estado incluyen: get = \s -> (s, s) – Examina el estado en este punto del cómputo
put s = \_ -> ((), s) – Reemplaza el estado
modify f = \s -> ((), f s) – Actualiza el estado
Otra operación aplica una mónada estado a un valor inicial dado: runState :: State s a -> s -> (a, s)
runState t s = t s
Los bloques-do en una mónada estado son secuencias de operaciones que pueden examinar y actualizar los datos de estado. Informalmente , una mónada estado de tipo S mapea el tipo de valores de retorno T en funciones de tipo , donde S es el estado subyacente. La función return es simplemente: La función bind (union) es:
Desde el punto de vista de la teoría de las categorías, una mónada estado es derivada de la conjunción entre el funtor producto y el funtor exponencial, el cual existe en cualquier categoría cartesiana cerrada por definición. Mónada ambienteLa mónada ambiente (también llamada “mónada lectora” y “mónada función”) permite que un computo dependa de los valores de un ambiente compartido . El constructor de tipo de mónada mapea un tipo T a funciones de tipo E → T', donde E es el tipo del ambiente compartido. Las funciones mónadas son: Las siguiente operaciones monádicas son las siguientes: La operación “ask” es usada para recuperar el contexto actual, mientras que local ejecuta una computación en un subcontexto modificado. Como en la mónada estado, los cómputos en la mónada ambiente pueden ser invocados al simplemente proveer un valor de ambiente y aplocarlo a una instancia de la mónada. Mónada escritoraLa mónada escritora le permite a un programa hacer computos de varios tipos de salidas auxiliares que pueden ser “compuestas” o “acumuladas” paso a paso, en adición al resultado general de la computación. Es usualmente requerida para capturar información. Dado el tipo subyacente T un valor en la mónada escritora tiene tipo W× T, donde W es un tipo con la operación adjuntada que satisface las leyes monóides. Las funciones mónadas son simplemente: donde ε y * son los elementos identidad del monoíde W y su operación asociada. La operación mónadica “tell” está definida por: : Donde 1 y () denotan el tipo unidad y su elemento trivial. Es usada en combinación con bind para actualizar el valor auxiliar sin afectar el computo principal. Mónada continuaciónUna mónada continuación con tipo de retorno mapea el tipo en funciones de tipo . Es usada para modelar el estilo de continuación de paso. Las funcinoes return y bind son las siguientes: La función llamada con-continuación actual está definida de la siguiente manera: OtrosOtros conceptos que los investigadores han expresado como mónada incluyen:
Co-mónadasLas co-mónadas son el dual categórico de las mónadas. Son definidas por un tipo de constructor W T y dos operaciones: extract (extraer) con tipo W T → T para cualquier T, y extend con tipo (W T → T' ) → W T → W T' . Las operaciones extract y extend deben satisfacer las leyes: Alternativamente las co-mónadas pueden ser definidas en términos de las operacinoes fmap, extract y duplicate. Las operaciones fmap y extract definen W, como el funtor coapuntado. La operación duplicate caracteriza a las co-mónadas: Tiene el tipo W T → W (W T) y sastisface las siguientes leyes: Las dos formulaciones son relacionadas de la siguiente manera: Mientras que las mónadas representan los efectos, una mónada W representa un tipo de contexto. Las funciones extract extraen un válor de éste contexto. Mientras que la función extend puede ser usada para componer una tubería de funciones dependientes del contexto de tipo W A → B. Co-mónada identidadLa co-mónada identidad es la más simple de las co-mónadas: mapea el tipo T a sí mismo. El operador extract' es la identidad y el operador extend es la función de aplicación. Co-mónada productoLa co-mónada producto mapea el tipo en tuplas de tipo , donde es el tipo contexto de la co-mónada. Las operaciones co-mónadas son: Función co-mónadaLa función co-mónada mapea tipo en funciones de tipo , donde es un tipo de conjunto con estructura monoide. Las operaciones co-mónadas son: donde ε es el elemento identidad de y * es la operación asociativa. Co-mónada co-estadoLa co-mónada co-estado mapea a tipo en tipo , donde S es el tipo base de la tienda. Las opreaciones de la co-mónada son: Véase también
Notas
Referencias
Enlaces externos
Tutoriales sobre mónadas en Haskell
Tutoriales viejos
Otra documentación
Tutoriales sobre mónadas en Scala
Mónadas en otros lenguajes |
Portal di Ensiklopedia Dunia