Monada (programacion funcional)

De Wikipedia, la enciclopedia libre

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:

  1. 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.

  1. 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.
  2. 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:
    1. La estructura relacionada con la monada en el primer argumento es atravesada para exponer cualquier numero de valores en el tipo subyacente t.
    2. La funcion dada es aplicada a todos esos valores para obtener valores de tipo (M u).
    3. La estructura de la monada en tales valores tambien es atravesada para revelar valores de tipo u.
    4. 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:
    • (
      return
       x
      )
       >>=
       f
        f
       x
      
      
    • m
       >>=
       return
        m
      
      
  • 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

:

  • return
     >=>
     g
      g
    
    
  • f
     >=>
     return
      f
    
    
  • (
    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.

mzero
 >>=
 f
  mzero

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 [ editar ]

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 ]

Notas [ editar ]

  1. 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 ]

  1. a b c O'Sullivan, Bryan; Goerzen, John; Stewart, Don. Real World Haskell . O'Reilly, 2009. ch. 14.
  2. ≪A physical analogy for monads≫ . Archivado desde el original el 10 de septiembre de 2010.  
  3. Eric Lippert. ≪Monads, part one≫ . Consultado el 6 de septiembre de 2013 .  
  4. Wadler, Philip . Comprehending Monads . Proceedings of the 1990 ACM Conference on LISP and Functional Programming, Nice. 1990.
  5. Wadler, Philip. The Essence of Functional Programming . Conference Record of the Nineteenth Annual ACM SIGPLAN-SIGACT Symposium on Principles of Programming Languages. 1992.
  6. Hughes, J. (2005). Programming with arrows . In Advanced Functional Programming (pp. 73-129). Springer Berlin Heidelberg. "
  7. De Meuter, Wolfgang. " Monads as a theoretical foundation for AOP ". Workshop on Aspect Oriented Programming, ECOOP 1997.
  8. a b Moggi, Eugenio (1991). ≪Notions of computation and monads≫ . Information and Computation 93 (1).  
  9. ≪Some Details on F# Computation Expressions≫ . Consultado el 14 de diciembre de 2007 .  
  10. ≪The Writer monad≫ . haskell.cz.  
  11. 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
  12. ≪Monad laws≫ . HaskellWiki . haskell.org . Consultado el 11 de diciembre de 2011 .  
  13. ≪Functors, Applicative Functors and Monoids≫ . learnyouahaskell.com.  
  14. 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 ]

Tutoriales viejos [ editar ]

Otra documentacion [ editar ]

Tutoriales sobre monadas en Scala [ editar ]

Monadas en otros lenguajes [ editar ]