Publicidad:
Terra
La Coctelera

un rato de sol

i will work harder, ja ja ja, no ahora en serio.

11 Diciembre 2008

Extendiendo clojure con mónadas

¿cómo pasar un rato aburrido? Implementemos un soporte para usar mónadas en clojure. Así, aunque no programemos en Haskell tendremos el respeto de cualquier tío con dos doctorados.

Primero, las definiciones de nuevo: ¿qué es una mónada?

Un triplete compuesto por

  • Un constructor de tipos que puede contener cualquier otro tipo en su interior: M t
  • Una función unitaria que introduce el tipo en el contenedor: t -> M t
  • Una función binding que enlaza funciones que consumen t y devuelven M t: >>= :: M t -> (t -> M u) -> M u

Bien. veamos como se puede implementar estas tres características en clojure.

Un constructor de tipos que puede contener cualquier otro tipo en su interior: M t

¿qué mejor que usar una estructura? Con dos campos, uno para guardar el tipo de la mónada y otro para el contenido:

 (defstruct Monad :monad-type :content)
 

Una función unitaria que introduce el tipo en el contenedor: t -> M t

Bueno, esto es sencillo, una simple función que coge el contenido y lo mete en la mónada del tipo corespondiente. Llamémosla "return", para seguir con las convenciones de Haskell:

 (defn return [monad-type value]
   (struct Monad monad-type value))
 

Una función binding que enlaza funciones que consumen t y devuelven M t: >>= :: M t -> (t -> M u) -> M u

Aquí tenemos un problema. La función de binding sirve para enlazar funciones monádicas en una sola computación que va transformando el contenido de la mónada con cada aplicación de las funciones. Esto sirve para modelar funciones que realizan IO, que mantienen un estado, etc. La semántica del operador de binding depende del tipo de la mónada.
Afortunadamente, clojure tiene un mecanismo de polimorfismo denominado multimétodos que permite dar diferentes implementaciones de una función según los argumentos que recibe. Definiremos por lo tanto la interfaz del método binding, y al escribir una nueva mónada daremos la implementación concreta:

 (defmulti >>= (fn [f m] (:monad-type m)))
 

Como se puede observar, la función de binding (>>= para seguir con la notación haskelliana) recibe dos parámetros la función f y una mónada. La implementación de la función se elegirá en función del valor :monad-type para el argumento m (la mónada).

Ya tenemo soporte para mónadas en clojure. Ahora sólo falta una mónada. Probemos con la primera mónada que usamos casi todos en Haskell: la mónada Maybe. Esta mónada se utiliza para envolver computaciones que pueden fallar, por ejemplo, porque tienen una componente aleatoria. Esta mónada puede tener dos valores Just x | Nothing, dependiendo de si la computación va bien (Just x) o falla (Nothing).

Implementemos la mónada. Primero dos constructores especiales para la mónada:

 (defn just [v]
   (return :Maybe v))
 
 (defn nothing []
   (return :Maybe :Nothing))
 

Ya podemos devolver Just X o Nothing.

Ahora un par de funciones auxiliares para comprobar si la mónada Nothing o un valor, y otra que extrae el valor de la mónada. No todas las mónadas tienen por qué tener estas funciones, pero vamos a darles implementación ya que estamos en ello:

 (defn nothing? [m]
   (= (:content m) :Nothing))
 
 (defn from-maybe [m]
   (:content m))
 

Es el momento de implementar la función de binding. Como dijimos, la semántica depende de la mónada en nuestro caso para enlazar funciones Maybe, comprobamos el valor de la mónada que le llega al operador de binding. Si este es Nothing, no llamamos a la función que vamos a enlazar, devolvemos Nothing simplemente. Si la mónada tiene valor, devolvemos el resultado de pasar ese valor a la función que recibimos como argumento y que devuelve otra mónada Maybe (que podrá ser a su vez Nothing o Just X, que podrá pasarse a otra función binding con otra función mónadica como argumento, etc...):

 (defmethod >>= :Maybe [f m]
   (if (nothing? m)
     (nothing)
     (f (:content m))))
 

Hemos terminado.

Bueno, antes vamos a ver un ejemplo de uso de la mónada Maybe. "check-random" es una función que acepta una cadena y que realiza el siguiente proceso: obtiene un número aleatorio entre 0-9. Si el número es menor que 5 lo añade a la cadena que recibe y devuelve "simplemente la nueva cadena" Just X. Si el número es mayor o igual a 5, no devuelve nada, es decir Nothing:

 (defn check-random 
   ([acumulator]
     (let [value (.. (new java.util.Random)
                   (nextInt 10))]
       (if (< value 5)
         (just (str acumulator value))
         (nothing))))
   ([] (check-random "")))
 

Escribamos una función que usa la función de binding para enlazar tres aplicaciones de "check-random" y que devuelve un bonito mensaje en función del resultado total:

 (defn test-maybe-monad []
   (let [result (>>= check-random
                  (>>= check-random
                    (>>= check-random
                      (just ""))))]
     (if (nothing? result)
       (println "No hubo suerte")
       (println (str "¡qué suerte! " (from-maybe result))))))
 

Un par de ejecuciones de ejemplo:

user=> (test-maybe-monad)
No hubo suerte
nil
user=> (test-maybe-monad)
¡qué suerte! 012
nil
user=>

¡Impresionante no es cierto!

Todo el ejemplo unido:

 (defstruct Monad :monad-type :content)
 
 (defmulti >>= (fn [f m] (:monad-type m)))
 
 (defn return [monad-type value]
   (struct Monad monad-type value))
 
 
 (defn nothing? [m]
   (= (:content m) :Nothing))
 
 (defn from-maybe [m]
   (:content m))
 
 (defn just [v]
   (return :Maybe v))
 
 (defn nothing []
   (return :Maybe :Nothing))
 
 (defmethod >>= :Maybe [f m]
   (if (nothing? m)
     (nothing)
     (f (:content m))))
 
 
 (defn check-random 
   ([acumulator]
     (let [value (.. (new java.util.Random)
                   (nextInt 10))]
       (if (< value 5)
         (just (str acumulator value))
         (nothing))))
   ([] (check-random "")))
 
 
 (defn test-maybe-monad []
   (let [result (>>= check-random
                  (>>= check-random
                    (>>= check-random
                      (just ""))))]
     (if (nothing? result)
       (println "No hubo suerte")
       (println (str "¡qué suerte! " (from-maybe result))))))
 
Tags: clojure, lisp, monada

servido por Antonio 2 comentarios compártelo

2 comentarios · Escribe aquí tu comentario

xcvb

xcvb dijo

celebrity guest rolex and has rolex watches appeared on programmes as diverse as TAB Sports Cafe (New Zealand), rolex watch Rove Live

18 Diciembre 2009 | 05:09 AM

fdsafd

fdsafd dijo

come !come my dear friends .come here buy fake rolex .cheap fake rolex at here .heave the replica rolex

3 Enero 2010 | 01:12 PM

Escribe tu comentario


Sobre mí

Avatar de Antonio

un rato de sol

Barcelona/Salamanca, España
ver perfil »
contacto »
Trabajador del metal y del acero, en la gloriosa XING AG, escribo software con el que poder ganarme el jornal. En mi tiempo libre sigo tecleando código de bonitos colores a medio camino entre lo sublime y lo terrible. Últimamente me gustan mucho los gatos.

Fotos

Antonio Garrote Hernández todavía no ha subido ninguna foto.

¡Anímale a hacerlo!

Buscar

suscríbete

Selecciona el agregador que utilices para suscribirte a este blog (también puedes obtener la URL de los feeds):

¿Qué es esto?

Crea tu blog gratis en La Coctelera