En la
programacion funcional
, una
monada
(
monad
en
ingles
), es un
patron de diseno
que representa calculos definidos como una secuencia de pasos, permitiendo componer funciones con tipos incompatibles encapsulandolos en un
tipo monadico
. Un
tipo de dato
con una estructura monada define lo que simboliza en un
bloque de codigo
, o anida
funciones
del mismo tipo. Esto le permite al programador crear
tuberias informaticas
que procesan datos en pasos, a los cuales se les asocia un
decorador
con reglas de proceso adicionales provistas por la monada.
[
1
]
Por lo tanto, las monadas han sido descritas como un “
punto y coma
programable”; un punto y coma, siendo el operador usado para unir varias declaraciones en la
programacion imperativa
,
[
1
]
en consecuencia la expresion implica que codigo extra sera ejecutado entre las declaraciones de una tuberia. Las monadas tambien han sido explicadas con un metafora fisica, donde se comportan como una linea de ensamblaje, en las que un objeto transporta datos entre unidades funcionales que la van transformando un paso a la vez.
[
2
]
Tambien se las puede ver como un
patron de diseno
funcional para construir
tipos genericos
.
[
3
]
Los programas puramente funcionales pueden usar las monadas para estructurar procedimientos que incluyan operaciones en secuencia como aquellos encontrados en la
programacion estructurada
.
[
4
]
[
5
]
Muchos conceptos de programacion comunes pueden ser descritos en terminos de estructuras de monadas, incluyendo los
efectos secundarios
, la
Entrada/salida
, asignacion de variables, el
manejo de excepciones
, el
analizador sintactico
, la programacion no deterministica,
concurrencia
y continuaciones. Esto permite que tales conceptos sean definidos en maneras puramente funcionales sin el uso de extensiones a la semantica del lenguaje. Los lenguajes como
Haskell
proveen monadas en el nucleo estandar, permitiendo a los programadores reutilizar largas partes de su definicion formal y aplicar en diversas librerias las mismas interfaces para combinar funciones.
[
6
]
Formalmente , una monada 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 composicion correcta de funciones
monadicas
(es decir, funciones que usen valores de la monada como sus argumentos o valores de retorno). La operacion
retorno
toma una valor de un tipo plano y lo pone en un contenedor monadico usando el constructor, creando asi un
valor monadico
. La operacion
unir
realiza el proceso inverso, extrayendo el valor original del contenedor y pasandolo a la siguiente funcion asociada en la tuberia, posiblemente con chequeos adicionales y transformaciones. Dado que una monada puede insertar operaciones adicionales en el dominio logico del programa, se les puede considerar como un tipo de
programacion orientada a aspectos
.
[
7
]
La
logica de negocio
puede ser definida por la programacion de aplicaciones en la tuberia, mientras que las operaciones a ser repetidas muchas veces pueden ser operadas por una monada predefinida construida previamente .
El nombre y concepto viene del eponimo (monada) en la
teoria de categorias
, donde las monadas son un tipo de
funtor
, o sea un mapeo entre categorias; aunque el termino monada en el contexto de la programacion funcional es usualmente tomado como correspondiente al termino monada fuerte en la teoria de categorias.
[
8
]
Historia
[
editar
]
El concepto de monada en la programacion aparecio 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 describio el uso general de las monadas para estructurar programas en 1991.
[
8
]
Mucha gente continuo su trabajo, incluyendo investigadores de lenguajes de programacion como Philip Wadler y Simon Peyton Jones (los cuales estuvieron involucrados en la especificacion de
Haskell
) Versiones tempranas de Haskell usaron una problematica “lista perezosa” para I/O, y Haskell 1.3 introdujo monadas de una manera mas flexible al combinar entrada/salida con
evaluacion perezosa
.
En adicion a la entrada/salida, los investigadores de los lenguajes de programacion y disenadores de librerias de Haskell han exitosamente aplicado las monadas a
evaluadores sintacticos
y a interpretes de lenguajes de programacion. El concepto de las monadas junto con el de la notacion
do
de Haskell ha sido generalizada para formar
funtores aplicativos
y
flechas
.
Por un largo tiempo, Haskell y sus derivados han sido los unicos usuarios mayores de monadas en la programacion. Tambien existen formulaciones en
Scheme
,
Perl
,
Python
, Racket,
Clojure
y
Scala
, donde las monadas se han presentado como una opcion de diseno de un nuevo lenguaje de programacion. Recientemente
F#
ha incluido la caracteristica de contener expresiones de computo o
workflows
, los cuales son un intento por introducir las construcciones monadicas dentro de una sintaxis mas palpable a los programadores con una experiencia en los lenguajes imperativos.
[
9
]
Ejemplos motivantes
[
editar
]
El lenguaje de programacion
Haskell
es un lenguaje funcional que hace un uso intensivo de monadas e incluye
azucar sintactico
para hacer la composicion de monadas mas conveniente. Todos los ejemplos de codigo escritos aqui son escritos en
Haskell
, a menos que se indique lo contrario.
Se demuestran dos ejemplos comunes en la introduccion a las monadas: la monada
Maybe
y la monada
IO
. Las monadas estan desde luego, no recluidas al lenguaje Haskell, el segundo ejemplo muestra la monada
Writer
en
JavaScript
.
La monada
Maybe
[
editar
]
Considerese la opcion 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
:
Just t
, conteniendo el valor
t
, o
Nothing
, sin contenido.
data
Maybe
t
=
Just
t
|
Nothing
Nos gustaria poder usar este tipo como una simple excepcion chequeada: en cualquier punto del computo, este puede fallar, lo que causa que el resto del proceso sea saltado y que el resultado final sea
Nothing
. Si todos los pasos del computo son exitosos, entonces el resultado final es
Just x
para un valor
x
.
En el siguiente ejemplo,
suma
es una funcion que toma dos argumentos del tipo
Maybe int
, y regresa un resultado del mismo tipo. Si ambas
mx
y
my
tienen valores
Just
, queremos regresar
Just
su suma; pero si
mx
o
my
son
Nothing
, queremos regresar
Nothing
. Si se intentara escribir funciones con este tipo de conducta, se terminaria con una serie de casos “if
Nothing
then
Nothing
else something with
x
in
Just x
” que rapidamente se volverian confusos:
[
1
]
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 computos de manera conjunta. El operador binario
bind
(“unir”) (
>>=
) encadena los resultados de un computo que pudiera fallar, a una funcion que elige otro computo que pudiera fallar. Si el argumento es
Nothing
(“nada”), el segundo argumento (la funcion) es ignorada y la operacion entera simplemente falla. Si el argumento es
Just x
(“solo x”), pasamos
x
a la funcion para obtener un nuevo valor
Maybe
, que puede o no ser el resultado en un valor del tipo
Just
(“solo”).
(
>>=
)
::
Maybe
a
->
(
a
->
Maybe
b
)
->
Maybe
b
Nothing
>>=
_
=
Nothing
-- Un computo fallido regresa Nada
(
Just
x
)
>>=
f
=
f
x
-- Aplica la funcion f al valor x
Usando un poco de
azucar sintactico
, conocida como notacion
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 operacion es muy comun, existe una funcion estandar en Haskell (
liftM2
) que toma dos valores monadicos (aqui: dos Maybes) y combina sus contenidos (dos numeros) usando otra funcion (adicion), haciendo posible escribir el ejemplo pasado como:
add
::
Maybe
Int
->
Maybe
Int
->
Maybe
Int
add
=
liftM2
(
+
)
La monada escritora
[
editar
]
La monada escritora permite que un proceso lleve informacion adicional “al lado” de su valor de computo. Esto puede ser util para el registro de errores o el debugging de la informacion que no es el resultado primario.
[
10
]
El siguiente ejemplo implementa la monada escrita en JavaScript:
Primero, una monada escritora es definida. La funcion
unidad
crea un nuevo tipo de valor de un tipo basico, el cual lleva una cadena de registro; y
bind
aplica la funcion a un valor viejo, y finalmente regresa el nuevo valor de resultado con un registro expandido. Los corchetes trabajan aqui como el constructor de tipo de la monada, creando un valor de tipo monadico para la monada escritora, hecha de componentes mas simples (el valor en la posicion 0 del arreglo, y la cadena de registro en la posicion 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
]];
};
Tuberia
es una funcion auxiliar que concatena una secuencia de
bind
s 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 monada 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 monada para crear una tuberia de funciones matematicas, con informacion de debug al lado.
pipeline
(
unit
(
4
),
[
squared
,
halved
]);
// [8, "al cuadrado.a la mitad"]
La monada IO
[
editar
]
En un lenguaje
puramente funcional
, como Haskell, las funciones no pueden tener efectos externos visibles como parte de la semantica de la funcion. Aunque una funcion 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 notacion Haskell, un valor de tipo
IO a
representa una accion que, a la hora de ser realizada , produce un valor de tipo
a'
.
Podemos pensar en un valor de tipo
IO
como una
accion
que toma como su argumento el estado actual del mundo, y que regresara un nuevo mundo donde el estado ha sido cambiado de acuerdo al valor de retorno de la funcion. Por ejemplo, las funciones
doesFileExist
(existe el archivo?) y
removeFile
(remueve el archivo) en la libreria estandar de Haskell tienen los siguientes tipos
doesFileExist
::
FilePath
->
IO
Bool
removeFile
::
FilePath
->
IO
()
Asi que uno puede pensar
removeFile
como una funcion que, dado un
FilePath
, regresa una accion
IO
; esta accion se encargara de que el mundo, en este caso el sistema de archivos, no tendra un archivo nombrado por tal
FilePath
cuando sea ejecutado. Aqui el valor interno
IO
es del tipo
()
que significa que el llamante no esta interesado en otros resultados. Por otra parte, en
doesFileExist
, la funcion regresa una
accion
IO
que envuelve a un valor booleano
True
o
False
; esto conceptualmente representa un nuevo estado del mundo donde el llamante sabe por cierto que un
FilePath
esta presente o no dentro del sistema de archivos en el momento que la
accion
fue realizada. El estado del mundo manejado de esta manera puede ser pasado de accion a accion, y de tal manera definir una serie de acciones que seran aplicadas en orden de pasos de cambio de estados. Este proceso es similar a como la
logica temporal
representa el paso del tiempo usando solo proposiciones declarativas. El siguiente ejemplo clarifica en detalle como este encadenamiento de acciones ocurre en un programa.
Nos gustaria ser capaces de describir todos los tipos basicos de operaciones I/O, por ejemplo, escribir texto a una salida estandar, leer de una entrada estandar, leer y editar archivos, mandar datos sobre redes, etc. En adicion, necesitamos ser capaces de componer estas primitivas para formar programas mas grandes. Por ejemplo, nos gustaria escribir :
main
::
IO
()
main
=
do
putStrLn
"Cual es tu nombre?"
name
<-
getLine
putStrLn
(
"Gusto en conocerte, "
++
name
++
"!"
)
¿Como formalizar esta notacion intuitiva? Para hacerlo, necesitamos ser capaces de realizar operaciones basicas de I/O:
- Debemos poder secuenciar dos operaciones I/O. En Haskell, esto es escrito como el operador infix
>>
, de tal manera que
putStrLn "abc" >> putStrLn "def"
sea un accion I/O que imprima dos lineas de texto hacia la consola. El tipo de
>>
es
IO a → IO b → IO b
, significando que el operador toma dos operaciones I/O y regresa una tercera que secualiza las dos y regresa el valor de la segunda.
- Debemos tener una accion I/O que haga
nada
. Es decir, regrese un valor pero no tenga efectos. En Haskell, este constructor es llamado
return
; tiene tipo
a → IO a
.
- Mas sutilmente, deberiamos poder determinar nuestra proxima accion basado en los resultados de la accion previa. Para hacer esto, Haskell tiene un operador
>>=
(pronunciado
bind
) con tipo
IO a → (a → IO b) → IO b
. Es decir, el operando de la izquierda es una accion I/O que regresa un valor del tipo
a
; el operando en la derecha es una funcion que puede tomar un accion I/O basada en el valor producido por la accion de la izquierda. La accion resultante combinada, al ser realizada, hace la primera accion, despues evalua la funcion con el valor de regreso de la primera y despues realiza la segunda accion, finalmente regresa el valor de la segunda accion.
- Un ejemplo del uso de este operador en Haskell seria
getLine >>= putStrLn
, el cual lee una sola linea de texto de una entrada estandar y la manda a la salida estandar. Notar que el primer operador,
>>
, es solo un caso especial de este operador en el que el valor de regreso de la primera accion es ignorado y la segunda accion seleccionada es siempre la misma.
No es necesariamente obvio que las tres operaciones precedentes, junto con set de operaciones I/O nos ayudan a definir
cualquier accion de programa
, incluyendo las transformaciones de datos, control de flujo y control de flujo recursivo. Podemos escribir el ejemplo de arriba como una sola expresion.
main
=
putStrLn
"Cual es tu nombre?"
>>
getLine
>>=
\
name
->
putStrLn
(
"Gusto en conocerte "
++
name
++
"!"
)
La estructura de tuberia del operador
bind
se asegura de que '
getLine
y
putStrLn
sean evaluadas una sola vez y en el orden requerido, para que asi los efectos de extraer el texto de la entrada y escribir a la salida sean correctamente ejecutadas en la tuberia funcional. Esto permanece cierto aun si el lenguaje realiza evaluacion de funciones fuera de orden o una
evaluacion perezosa
.
Claramente , existe una estructura comun entre las definiciones I/O y las Maybe, aun si son diferentes en muchas maneras. Las monadas son una abstraccion de las estructuras antes descritas, de estructuras similares y de sus semejanzas. El concepto general de monadas incluye una situacion donde el programador quiere llevar a cabo un computo puramente funcional mientras que un computo relacionado se lleva a cabo al lado.
Definicion Formal
[
editar
]
Una monada es una construccion que, dada un tipo de sistema, embebe uno correspondiente (llamo el
tipo de sistema monadico
). Tal tipo de sistema monadico preserva todos los aspectos significativos del tipo de sistema subyacente, mientras que tambien le anade caracteristicas particulares de la monada.
[
note 1
]
La formulacion usual de una monada para programacion es conocida como una Kleisli triple, y tiene los siguientes componentes:
- Un
tipo de constructor
que define, para cada tipo subyacente, el como obtener un correspondiente tipo monadico. En la notacion de Haskell, el nombre de la monada representa el tipo de constructor.
Si
M
es el nombre de la monada y
t
es un tipo de dato, entonces
M t' es el correspondiente tipo en la monada.
- Una
funcion unidad
que mapee un valor de un tipo subyacente a un valor en el correspondiente tipo monadico. La funcion unidad tiene el tipo polimorfico
t→M t
. El resultado es normalmente el valor
mas simple
en el tipo correspondiente que completamente preserva el valor original. En Haskell, esta funcion es llamada
return
debido a la manera en que es usada en la notacion-do.
- Un
operador union
de tipo polimorfico
(M t)→(t→M u)→(M u)
, el cual Haskell representa con el operador de
notacion de infijo
>>=
. Su primer argumento es un valor de tipo monadico, su segundo argumento es una funcion que mapea el tipo subyacente del primer argumento a otro tipo monadico. Tipicamente, el operador union puede ser entendido en 4 etapas:
- La estructura relacionada con la monada en el primer argumento es
atravesada
para exponer cualquier numero de valores en el tipo subyacente t.
- La funcion dada es aplicada a todos esos valores para obtener valores de tipo (M u).
- La estructura de la monada en tales valores tambien es atravesada para revelar valores de tipo u.
- Finalmente, la estructura relacionada con la monada es reensamblada con todos los resultados, dando asi un solo valor de tipo (M u).
Dado un constructor de tipo
M
, en la mayoria de los contextos, un valor de tipo
M a
puede ser pensado como una accion que regresa un valor de tipo
a
. La operacion de
retorno
toma un valor del tipo plano
a
y lo pone en un contenedor monadico de tipo
M a
; la operacion
union
encadena el valor monadico de tipo 'M a
con una funcion de tipo
a → M b
para crear un valor monadico de tipo
M b
.
Leyes monadicas
[
editar
]
Para que una monada se comporte de la manera correcta, sus definiciones deben seguir ciertos axiomas, los cuales en su conjunto son llamados leyes monadicas.
[
12
]
El simbolo ≡ indica equivalencia entre dos expresiones de Haskell en el siguiente texto.
- return
actua aproximadamente como un elemento neutral de
>>=
, en que:
- El unir dos funciones en sucesion es lo mismo que unir una funcion que puede ser determinada desde ellas:
(
m
>>=
f
)
>>=
g
≡
m
>>=
(
\
x
->
(
f
x
>>=
g
)
)
Los axiomas tambien pueden ser mostrados usando las expresiones en la notacion-do:
do
{
f
x
}
≡
do
{
v
<-
return
x
;
f
v
}
do
{
m
}
≡
do
{
v
<-
m
;
return
v
}
do
{
x
<-
m
;
y
<-
f
x
;
g
y
}
≡
do
{
y
<-
do
{
x
<-
m
;
f
x
};
g
y
}
o usando el operador monadico de composicion,
(
f
>=>
g
)
x
=
(
f
x
)
>>=
g
:
(
f
>=>
g
)
>=>
h
≡
f
>=>
(
g
>=>
h
)
fmap
y
join
[
editar
]
Aunque Haskell define a las monadas en terminos de las funciones
return
y
union
, tambien es posible definir una monada en terminos de
retorno
y otras dos operaciones,
join
(juntar) y
fmap
. Esta formulacion se encuentra mas apegada con la definicion de monadas en la teoria de categorias. La operacion
fmap
, con tipo (
t
→
u
) → M
t
→M
u
,
[
13
]
toma una funcion entre 2 tipos y produce una funcion que
hace la misma cosa
a los valores en la monada. La operacion
join
, con tipo M (M
t
)→M
t
,
aplana
dos capas de informacion monadica en una sola.
Las dos formulaciones son mostradas:
fmap
f
m
=
m
>>=
(
return
.
f
)
join
n
=
n
>>=
id
m
>>=
g
≡
join
(
fmap
g
m
)
Aqui, 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 funcion
retorno
caracteriza los funtores apuntados en la misma categoria, usando para esto la habilidad de
cargar
valores en el funtor. Se debe satisfacer la siguiente ley:
return
.
f
≡
fmap
f
.
return
En adicion , la funcion
join
caracteriza las monadas:
join
.
fmap
join
=
join
.
join
join
.
fmap
return
=
join
.
return
=
id
join
.
fmap
(
fmap
f
)
=
fmap
f
.
join
Monadas aditivas
[
editar
]
Una
monada aditiva
es una monada provista con un cero monadico
mzero
y un operador binario
mplus
que satisface las leyes monadicas, usando el cero monadico como unidad. El operador
mplus
tiene el tipo
M t
→
M t
→
M t
(donde
M
es el constructor monadico 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 monada aditiva es tambien un
monoide
).
El unir el
mzero
con cualquier funcion produce el cero para el tipo de resultado, justo como el multiplicar 0 por cualquier numero da 0.
Similarmente, unir cualquier
m
con una funcion que siempre devuelve un 0, resulta en un 0.
m
>>=
(
\
x
->
mzero
)
≡
mzero
Intuitivamente, el cero representa un valor en la monada que tiene una estructura exclusivamente relacionada con la monada y que no tiene valores de tipo subyacente. En la monada Maybe,
Nothing
(Nada) es un cero. En la monada Lista,
[ ]
(la lista vacia) es tambien un cero.
Azucar sintactico: notacion
do
[
editar
]
Aunque hay ocasiones en las que tiene sentido usar el operador
union
>>=
directamente en un programa, es mas tipico usar la llamada
notacion
do
(
performn-notation
en
Ocaml
,
expresiones de computacion
en
F Sharp
, que mimetiza la apariencia de los lenguajes imperativos. El compilador traduce la notacion-do a expresiones que incluyen
>>=
. Por ejemplo, el siguiente codigo:
a
=
do
x
<-
[
3
..
4
]
[
1
..
2
]
return
(
x
,
42
)
es transformado durante la compilacion a:
a
=
[
3
..
4
]
>>=
(
\
x
->
[
1
..
2
]
>>=
(
\
_
->
return
(
x
,
42
)))
Es util ver la implementacion de la monada lista. Y saber que concatMap mapea una funcion 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
)]
Notese que la lista
[1..2]
no se usa. La falta de flecha apuntando a la izquierda, traducida a una funcion de union que ignora su argumento, indica que solo la estructura monadica es de interes, no los valores dentro de ella. Por ejemplo, para una monada estado esto puede ser usado para cambiar su estado sin producir mas valores. El bloque de notacion-do puede ser usado con cualquier monada ya que solo es azucar sintactica para:
>>=
.
Las siguientes definiciones para la division segura de valores en la monada Maybe tambien son equivalentes:
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 expresion de computacion:
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 azucar sintactica del bloque maybe seria traducido internamente a la siguiente expresion:
maybe
.
Delay
(
fun
()
->
maybe
.
Bind
(
readNum
()
,
fun
x
->
maybe
.
Bind
(
readNum
()
,
fun
y
->
if
(
y
=
0
)
then
None
else
maybe
.
Return
(
x
/
y
))))
Funciones monadicas genericas
[
editar
]
Dados los valores producidos por la division segura, podriamos querer seguir haciendo calculos sin tener que checar manualmente si son
Nothing
(es decir el resultado de intentar hace runa division por 0). Podemos hacer lo anterior usando una funcion
levantante
, la cual podemos definir no solo para la monada
Maybe
sino tambien para monadas 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, asi que
liftM2
es una funcion que toma una funcion binaria como argumento y regresa otra funcion binaria. El tipo signature dice: Si
m
es una monada, podemos
levantar
cualquier funcion 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 numeros, a menos que uno de ellos sea
Nothing
(en cuyo caso regresa
Nothing
). La ventaja aqui es que no necesitamos adentrarnos en los detalles de la implementacion de la monada; si necesitamos hacer lo mismo con otra funcion, o en otra monada, el usar
liftM2
hace inmediatamente claro lo que se intenta hacer.
Matematicamente, el operador liftM2 esta definido por:
Otros ejemplos
[
editar
]
Monada identidad
[
editar
]
La monada mas simple es la monada identidad, la cual no anade informacion a los valores.
Id
t
=
t
return
x
=
x
x
>>=
f
=
f
x
Un bloque-do en esta monada realiza substitucion de variables;
do {x <- 2; return 3*x}
resulta en 6.
Desde el punto de vista de la teoria de categorias, la monada identidad deriva de la union entre funtores identidad.
Colecciones
[
editar
]
Algunos tipos familiares de coleccion, incluyendo las listas, los sets y multisets, son monadas. La definicion para las listas estan dadas aqui.
-- "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 vacia
mzero
=
[]
Las listas de comprension, son un aplicacion especial de la monada lista. Por ejemplo, la lista comprension
[ 2*x | x <- [1..n], isOkay x]
corresponde al computo en la monada lista
do {x <- [1..n]; if isOkay x then return (2*x) else mzero;}
.
La notacion de listas por comprension es similar a la notacion de constructor de conjunto, pero un conjunto no puede ser convertido en una monada, ya que hay una restriccion en el tipo de computo a ser comparable por igualdad, mientras que una monada no pone ninguna restriccion en los tipos de computo. De hecho, el conjunto es una
monada restringida
.
[
14
]
Las monadas de colecciones representan naturalmente un
algoritmo no determinista
. La lista (u otra coleccion) representa todos los posibles resultados de diferentes caminos no deterministicos de computo en ese momento. Por ejemplo, cuando uno ejecuta
x <- [1,2,3,4,5]
, uno dice que la variable
x
puede de manera no deterministica tomar cualquiera de los valores de la lista. Si uno regresara
x
se evaluara con una lista de resultados de cada camino de computo. Notese que el operador union de arriba sigue tal tema al realizar
f
en cada uno de los resultados posibles y despues concatena las listas resultantes.
Las declaraciones como
if condition x y then return () else mzero
son usualmente vistas; si la condicion es verdad, la opcion no deterministica se realiza desde un camino dummy de computo, el cual regresa un valor que no asignamos a nada; sin embargo, si la condicion es falsa, entonces la monada de valor no deterministico
mzero = []
escoge desde 0 valores, terminando asi efectivamente tal camino de computo. Otros caminos de computo pueden todavia tener exito. Esto sirve como una
guarda
para asegurar que solo los caminos de computo que satisfacen ciertas condiciones puedan continuar. Asi que las monadas coleccion son muy utiles para resolver puzles logicos, Sudoku, y problemas similares.
En un lenguaje con
evaluacion 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 sera computado. Con respecto al uso de la monada lista para computos no deterministicos, esto significa que podemos generar no deterministicamente una lista perezosa de todos los resultados del computo, despues pedir el primero de ellos, y solo se realizara tanto trabajo sea necesario para obtener el primer resultado. El proceso vagamente corresponde a regresarse: un camino es escogido, despues falla en cierto punto (si es que se evalua
mzero
), posteriormente se regresa al ultimo punto de ramificacion, y sigue el otro camino, y asi hasta terminar. Si el segundo elemento es pedido, se vuelve a hacer solo el trabajo necesario para obtener la segunda solucion. De tal manera que la monada lista es una manera sencilla de implementar un algoritmo de regreso en un lenguaje perezoso.
Desde el punto de vista de la teoria de las categorias, las monadas coleccion son derivadas una union de funtores entre uno libre y uno subyacente entre la categoria de conjuntos y la categoria de
magma (categoria)
. Tomando diferentes tipos de magmas, obtenemos diferentes tipos de colecciones, como se muestra en la tabla de abajo:
Tipos de colecciones
|
Tipos de monoides
|
lista
|
monoide
|
multiset finito
|
monoide conmutativo
|
set finito
|
monoide conmutativo
idempotencia
|
permutacion finita
|
monoide conmutativo
idempotencia
|
Monadas estado
[
editar
]
Una monada estado le permite a un programador anadir informacion de estado de cualquier tipo de calculo. Dado cualquier tipo de valor, el tipo correspondiente en la monada estado es una funcion la cual acepta estado y despues regresa un nuevo estado (de tipo
s
) junto con un valor de retorno (de tipo
t
).
type
State
s
t
=
s
->
(
t
,
s
)
Notese que en esta monada, en comparacion con las ya vistas, toma un parametro tipo, o sea el estado de tipo de la informacion. Las operaciones de la monada 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 utiles de estado incluyen:
get
=
\
s
->
(
s
,
s
)
?
Examina
el
estado
en
este
punto
del
computo
put
s
=
\
_
->
(
()
,
s
)
?
Reemplaza
el
estado
modify
f
=
\
s
->
(
()
,
f
s
)
?
Actualiza
el
estado
Otra operacion aplica una monada estado a un valor inicial dado:
runState
::
State
s
a
->
s
->
(
a
,
s
)
runState
t
s
=
t
s
Los bloques-do en una monada estado son secuencias de operaciones que pueden examinar y actualizar los datos de estado.
Informalmente , una monada estado de tipo
S
mapea el tipo de valores de retorno
T
en funciones de tipo
, donde
S
es el estado subyacente. La funcion
return
es simplemente:
La funcion
bind
(union) es:
- .
Desde el punto de vista de la teoria de las categorias, una monada estado es derivada de la conjuncion entre el funtor producto y el funtor exponencial, el cual existe en cualquier categoria cartesiana cerrada por definicion.
Monada ambiente
[
editar
]
La monada ambiente (tambien llamada “monada lectora” y “monada funcion”) permite que un computo dependa de los valores de un ambiente compartido . El constructor de tipo de monada mapea un tipo
T
a funciones de tipo
E
→
T', donde E es el tipo del ambiente compartido. Las funciones monadas son:
Las siguiente operaciones monadicas son las siguientes:
La operacion “ask” es usada para recuperar el contexto actual, mientras que
local
ejecuta una computacion en un subcontexto modificado. Como en la monada estado, los computos en la monada ambiente pueden ser invocados al simplemente proveer un valor de ambiente y aplocarlo a una instancia de la monada.
Monada escritora
[
editar
]
La monada escritora le permite a un programa hacer computos de varios tipos de salidas auxiliares que pueden ser “compuestas” o “acumuladas” paso a paso, en adicion al resultado general de la computacion. Es usualmente requerida para capturar informacion. Dado el tipo subyacente
T
un valor en la monada escritora tiene tipo
W
×
T
, donde
W
es un tipo con la operacion adjuntada que satisface las leyes monoides. Las funciones monadas son simplemente:
donde ε y * son los elementos identidad del monoide
W
y su operacion asociada.
La operacion monadica “tell” esta definida por: :
Donde 1 y () denotan el tipo unidad y su elemento trivial. Es usada en combinacion con
bind
para actualizar el valor auxiliar sin afectar el computo principal.
Monada continuacion
[
editar
]
Una monada continuacion con tipo de retorno
mapea el tipo
en funciones de tipo
. Es usada para modelar el estilo de continuacion de paso. Las funcinoes return y bind son las siguientes:
La funcion llamada con-continuacion actual esta definida de la siguiente manera:
Otros conceptos que los investigadores han expresado como monada incluyen:
Co-monadas
[
editar
]
Las co-monadas son el dual categorico de las monadas. 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-monadas pueden ser definidas en terminos de las operacinoes
fmap
,
extract
y
duplicate
. Las operaciones
fmap
y
extract
definen
W
, como el funtor coapuntado. La operacion
duplicate
caracteriza a las co-monadas:
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 monadas representan los efectos, una monada
W
representa un tipo de
contexto
. Las funciones
extract
extraen un valor de este contexto. Mientras que la funcion
extend
puede ser usada para componer una tuberia de
funciones dependientes del contexto
de tipo W
A
→
B
.
Co-monada identidad
[
editar
]
La co-monada identidad es la mas simple de las co-monadas: mapea el tipo
T
a si mismo. El operador
extract'
es la identidad y el operador
extend
es la funcion de aplicacion.
Co-monada producto
[
editar
]
La co-monada producto mapea el tipo
en tuplas de tipo
, donde
es el tipo contexto de la co-monada. Las operaciones co-monadas son:
Funcion co-monada
[
editar
]
La funcion co-monada mapea tipo
en funciones de tipo
, donde
es un tipo de conjunto con estructura monoide. Las operaciones co-monadas son:
donde ε es el elemento identidad de
y * es la operacion asociativa.
Co-monada co-estado
[
editar
]
La co-monada co-estado mapea a tipo
en tipo
, donde S es el tipo base de la tienda. Las opreaciones de la co-monada son:
Vease tambien
[
editar
]
- ↑
Tecnicamente, la monada no es requerida para preservar el tipo subyacente. Por ejemplo, la monada trivial en la que solo hay un valor polimorfico , el cual es producido por todas las operaciones, satisface todos los axiomas de la monada. De la misma manera, la monada no es requerida para anadir estructuras adicionales; la monada identidad, la cual simplemente preserva el tipo original sin cambios, tambien satisface los axiomas de la monada y es util como una base recursiva para los tranformadores monadicos.
Referencias
[
editar
]
- ↑
a
b
c
O'Sullivan, Bryan; Goerzen, John; Stewart, Don.
Real World Haskell
. O'Reilly, 2009. ch. 14.
- ↑
≪A physical analogy for monads≫
. Archivado desde
el original
el 10 de septiembre de 2010.
- ↑
Eric Lippert.
≪Monads, part one≫
. Consultado el 6 de septiembre de 2013
.
- ↑
Wadler, Philip
.
Comprehending Monads
. Proceedings of the 1990 ACM Conference on LISP and Functional Programming, Nice. 1990.
- ↑
Wadler, Philip.
The Essence of Functional Programming
. Conference Record of the Nineteenth Annual ACM SIGPLAN-SIGACT Symposium on Principles of Programming Languages. 1992.
- ↑
Hughes, J. (2005).
Programming with arrows
. In Advanced Functional Programming (pp. 73-129). Springer Berlin Heidelberg. "
- ↑
De Meuter, Wolfgang. "
Monads as a theoretical foundation for AOP
". Workshop on Aspect Oriented Programming, ECOOP 1997.
- ↑
a
b
Moggi, Eugenio
(1991).
≪Notions of computation and monads≫
.
Information and Computation
93
(1).
- ↑
≪Some Details on F# Computation Expressions≫
. Consultado el 14 de diciembre de 2007
.
- ↑
≪The Writer monad≫
. haskell.cz.
- ↑
Peyton Jones, Simon L.
; Wadler, Philip.
Imperative Functional Programming
. Conference record of the Twentieth Annual ACM SIGPLAN-SIGACT Symposium on Principles of Programming Languages, Charleston, South Carolina. 1993
- ↑
≪Monad laws≫
.
HaskellWiki
. haskell.org
. Consultado el 11 de diciembre de 2011
.
- ↑
≪Functors, Applicative Functors and Monoids≫
. learnyouahaskell.com.
- ↑
How to make Data.Set a monad
shows an implementation of the Set restricted monad in
Haskell
Enlaces externos
[
editar
]
Tutoriales sobre monadas en Haskell
[
editar
]
- Monad Tutorials Timeline
Probably the most comprehensive collection of links to monad tutorials, ordered by date.
- Piponi, Dan (7 de agosto de 2006).
≪You Could Have Invented Monads! (And Maybe You Already Have.)≫
.
A Neighborhood of Infinity
.
? The most famous "blog post" tutorial.
- Yorgey, Brent (12 de marzo de 2009).
≪The Typeclassopedia≫
.
The Monad.Reader
(13): 17-68.
? An attempt to explain all of the leading typeclasses in Haskell in an elementary way, with monadic functors considered as only one form, best understood by comparison with others: e.g., the more general idea of a "Functor" as something you can map over; "Applicative" functors, and so forth; contains an extensive bibliography.
- Yorgey, Brent (12 de enero de 2009).
≪Abstraction, intuition, and the "monad tutorial fallacy
"
≫
.
blog :: Brent -> [String]
.
WordPress.com
.
? Opposes the idea of making a tutorial about monads in particular.
- What a Monad is not
deals with common misconceptions and oversimplifications in a humorous way.
- beelsebob (31 de marzo de 2009).
≪How you should(n’t) use Monad≫
.
No Ordering
.
WordPress.com
.
? Takes a similar point of view, locating monads in a much wider array of Haskell functor classes, of use only in special circumstances.
- Vanier, Mike (25 de julio de 2010).
≪Yet Another Monad Tutorial (part 1: basics)≫
.
Mike's World-O-Programming
.
LiveJournal
.
? An extremely detailed set of tutorials, deriving monads from first principles.
- ≪A Fistful of Monads≫
.
An explanation of Monads, building on the concepts of Functors, Applicative Functors and Monoids discussed in the
previous chapter
.
- Functors, Applicatives and Monads in Pictures
. A humorous beginner's guide to monads.
Tutoriales viejos
[
editar
]
Otra documentacion
[
editar
]
Tutoriales sobre monadas en Scala
[
editar
]
Monadas en otros lenguajes
[
editar
]