Publicidad:
La Coctelera

un rato de sol

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

24 Enero 2009

El núcleo (mónadico) de un framework web clojure

En las últimas semanas, mi pequeña biblioteca clojure para grizzly ha ido creciendo y ya puede aceptar cualquier petición web y renderizar una respuesta.

Desde el punto de vista de cliente, el proceso es ahora mismo este:

Enrutamos una petición a una función que procesará la petición:

  (route! (url-pattern GET "greetings" :name)
             say-hello)
 

Con la anterior expresión estamos enrutando las peticiones "GET /greetings/PARAMETRO" a la función say-hello, que además recibirá el valor de PARAMETRO con el nombre :name.

Definamos ahora la función say-hello:

 (defn say-hello
   ([] (render (if (= (parameter :name) "Helena")
                        "Hola chica guapa!"
                        (str "Hola " (parameter :name)))
                    200
                    {:Content-Type "text/plain"})))
 

En esta función básicamente decimos que queremos renderizar un mensaje u otro en función del valor del parámetro :name de la URL.


Hasta aquí todo sencillo, pero lo interesante es que hay debajo de todo esto ¿qué hacen las llamadas a render y parameter? Bien, la respuesta es que toda la lógica de servicio de una petición se encuentra encerrado dentro de una mónada, la Mónada WebIO. Esta mónada se define como:

 ;;
 ;; WebIO X = WebIOUnfinished X |
 ;;           WebIOFinished X
 ;;           WebIOtFailed
 ;;
 
 (defstruct web-request-processing :environment ;; The Rack request
                                   :response ;; The response of the request
                                   :parameters) ;; the parameters hash
 

La petición, la respuesta y los parámetros se guardan en la mónada que puede tener tres estados pendiente, terminada o fallida que establecen el estado de la respuesta.

La función de binding para la mónada WebIO, define como se deben combinar funciones monádicas según el resultado de aplicar la petición contenida en la mónda:

 (defmethod >>= :WebIO [f m]
   "Instance of the >>= function for the WebIO monad. If the computation
    result of m is WebIO Unfinished, the next function f is applied to the
    content of the m. If the result of m is WebIO Failed, next function
    f is not applied and m is returned. If the result of m is WebIO
    Finished, f is not invoked"
   (if (or (web-io-failed? m) (web-io-finished? m))
     m
     (f (:content m))))
 

Para poder realizar el procesamiento de la petición sólo se necesitan dos cosas una función que transforme una petición web, a través de la interfaz Rack que usamos, en una mónada WebIO y algún método para transformar una función normal como la anterior usada como ejemplo (say-hello), que no sabe nada sobre mónada alguna en una función monádica capaz de ser procesada por el operador de binding (>>=).

De lo primero se encarga la función (wrap-request) que usa la función general monádica (return) para envolver una petición Rack, además de protegerla con una referencia para poder ser usada facilmente mediante el mecanismo de memoria transaccional software (STM) que implementa Clojure:

 (defn wrap-request
   "Wrapper around return that builds the new request monad initializing it
    with the values of the rack-request"
   {:monad :WebIO}
   ([rack-request rack-response parameters subtype]
      (return :WebIO subtype
            (struct web-request-processing
                    rack-request
                    (if (= (class rack-response) clojure.lang.Ref)
                      rack-response
                      (ref rack-response))
                    parameters)))
   ([rack-request]
     (wrap-request rack-request (create-rack-response) {} :Unfinished))
   ([rack-request rack-response]
     (wrap-request rack-request rack-response {} :Unfinished))
   ([rack-request rack-response parameters]
     (wrap-request rack-request rack-response parameters :Unfinished)))
 

El segúndo requisito, transformar una función normal en una función monádica, lo realiza la función (with-web-io) y se conoce normalmente como "lifting". En este caso además, define unas variables accesibles sólo por el hilo que ejecuta esa función, para la petición, respuesta y parámetros, de forma que no sea necesario para el programador que implementa la función con la lógica de procesamiento, tener que pasar explícitamente estos parámetros a funciones como (parameter) o (render):

 (defn with-web-io
   "Lifts a call to a function for a web-io-processing struct into the web-io monad"
   {:monad :WebIO}
   ([f web-io-processing]
      (try
       (let [result (binding [*request* (:environment web-io-processing)
                              *response* (:response web-io-processing)
                              *parameters* (:parameters web-io-processing)]
                      (apply f []))]
         (if (web-io-monad? result)
           result
           (wrap-request (:environment web-io-processing)
                         (:response web-io-processing)
                         (:parameters web-io-processing)
                         :Unfinished)))
       (catch Exception ex (raise (:environment web-io-processing)
                                  500
                                  (. ex getMessage))))))
 

Sobre estas funciones, el mecanismo que sirve una petición resulta trivial, el proceso cuenta con los siguientes pasos:

  • Recibir la petición Rack
  • Extraer la ruta y método de la petición y comprobar si se puede enrutar a alguna función de respuesta
  • Transformar la petición Rack en una mónada WebIO
  • Crear una colección con los filtros anteriores, la función de respuesta, y los filtros posteriores a los que se va a enrutar la petición
  • Hacer un lift de cada una de estas funciones a funciones monádicas a través de (with-web-io)
  • Ejecutar la secuencia de funciones monádicas con el operador de binding (>>=)
  • Devolver el resultado a través de Rack

servido por Antonio 1 comentario compártelo

12 Enero 2009

Programación concurrente en Clojure: agentes

Clojure es un lenguaje de programación pensado por y para la concurrencia. Las características funcionales del lenguaje, como la inmutabilidad de sus estructuras de datos, contribuyen a que mantener varios hilos de ejecución sea sencillo, cada uno con su propia copia inmutable de los datos, ya que de esta manera, es imposible que un hilo modifique el estado de la computación de otro hilo.

Sin embargo, hay veces que es necesario compartir el estado entre varios hilos o, dado que Clojure se ejecuta sobre la máquina virtual de Java y es posible acceder a todos los objetos y clases de Java, trabajamos con estructuras de datos no inmutables.

Clojure ofrece diferentes formas para tratar estas situaciones automáticamente, de forma que se eliminen los problemas de concurrencia que aparecen en estas circunstancias, los agentes son una de ellas.

Un agente es una abstracción que mantiene el estado de una computación, actualizándolo mediante la ejecución de forma asíncrona de funciones sobre ese estado que son enviadas por otros hilos mediante la función (send). Estas funciones se van almacenando en una estructura FIFO y van siendo aplicadas secuencialmente por el agente en su propio pool de hilos.
Si habéis trabajado con Erlang, podéis pensar en los agentes Clojure como una versión recortada de los servidores Erlang.

Un agente clojure se declara con la función (agent):

 (agent initial-state)
 

Donde initial-state puede ser cualquier estructura de datos. Por ejemplo, vamos a construir un logger asíncrono y thread-safe. Nuestro logger mantendrá pues un nivel de logging y un sitio donde escribir el log, por defecto le daremos el nivel "1" y la salida estándar:

(def *logger* (agent {:level 1 :output *out*}))
 

Para loguear los mensajes, necesitamos una función (log), que recibirá un nivel de logging y algo que loguear, como usar números para los niveles de log queda algo feo, definamos una función que transforma claves para los niveles de log en enteros:

 (defn level-name-to-int
   "Returns a numeric identifier for the levels of the logger"
   ([level]
      (let [levels {:debug 0
                    :info 1
                    :warning 2
                    :error 3
                    :critical 4}]
        (let [to-return (level levels)]
          (if (nil? to-return) 6 to-return)))))
 

Ahora que tenemos niveles de log, necesitamos alguna forma de cambiar el nivel de logging actual almacenado en el agente del logger. Como ya mencionamos, para cambiar el estado de un agente, necesitamos una función que será enviada al agente para que la ejecute en su pool de hilos sobre el estado actual almacenado. La función de actualización debe recibir como primer argumento el estado del agente, una serie de parámetros opcionales y devolver un estado del agente modificado. Usando notación Haskell:

 updateFunction :: AgentState -> [Args] -> AgentState
 

En nuestro caso, nuestra función para modificar el nivel de logging del agente puede ser:

 (defn reset-logger-level
   "Initializes the logger with a given level"
   ([level]
      (send *logger* (fn [a l] {:level (level-name-to-int l) :output (:output a)}) level)))
 

Como se puede ver, enviamos al agente *logger* una función lambda, donde se transforma la estructura de dats almacenada en el agente, sustituyendo el viejo nivel de logeo con el suministrado en el argumento "level".

Ahora, necesitamos la función de logueo. Crearemos dos versiones, una que recibe un nivel y el código a loguear, y si el nivel del agente logger es adecuado loguea el resultado del código pasado a la función de log y lo devuelve tal cual. La segunda, recibirá además una descripción que logueará junto al resultado de ejecutar el códigos suministrado como argumento:

 (defn log
   "logs something in the logger with the provided level of log"
   ([level to-log]
      (do
        (send *logger* (fn [a level msg]
                         (do
                           (when (<= (:level a)
                                     (level-name-to-int level))
                             (let [to-write (str (. (keyword-to-string level) toUpperCase) " "
                                                 (. (new java.util.Date) toString) " => "(str msg) "\n")
                                   writer (:output a)]
                               (do
                                 (. writer (write to-write 0 (. to-write length)))
                                 (. writer flush))))
                           a))
              level to-log)
        to-log))
   ([level desc to-log]
      (do
        (send *logger* (fn [a level msg]
                         (do
                           (when (<= (:level a)
                                     (level-name-to-int level))
                             (let [to-write (str (. (keyword-to-string level) toUpperCase) " "
                                                 (. (new java.util.Date) toString) " => " desc " " (str msg) "\n")
                                   writer (:output a)]
                               (do
                                 (. writer (write to-write 0 (. to-write length)))
                                 (. writer flush))))
                           a))
              level to-log)
        to-log)))
 

En este caso, como en el anterior, enviamos una función lambda al agente con send para que haga el trabajo, esta función loguea, el resultado de la ejecución comprobando el nivel de log y añadiendo alguna información extra como la hora.

Es interesante notar que la función log no es bloqueante, en cuanto se ha enviado la función lambda al agente, retorna y la ejecución de la computación continúa, se haya logueado el mensaje o no, además como la ejecución de funciones en el pool del agente es FIFO, los mensajes se van a loguear en el orden de ejecución, sin que se "cuelen" mensajes en el orden de ejecución (la función send es atómica).

Aquí se puede comprobar como funciona en ejecución nuestro flamante logger:

 user=> (reset-logger-level :debug)
 (reset-logger-level :debug)
 #=(clojure.lang.Agent. "clojure.lang.Agent@219009")
 
 user=> (log :info "hola?" (+ 1 2))
 (log :info "hola?" (+ 1 2))
 3
 
 user=> INFO Mon Jan 12 00:25:54 CET 2009 => hola? 3
 (reset-logger-level :error)
 (reset-logger-level :error)
 #=(clojure.lang.Agent. "clojure.lang.Agent@219009")
 
 user=> (log :info "ahora no... " (* 3 3))
 (log :info "ahora no... " (* 3 3))
 9
 user=>  
 

Como se puede ver, se puede meter llamadas a log en medio de la ejecución del código ya que es transparente para una llamada a función, además se puede comprobarla ejecución asíncrona del logger, comprobando como se cuela el logueo de la llamada para (log :info "hola?" (+ 1 2)) antes de que la shell de clojure muestre el cursor para la siguiente entrada de datos por parte del usuario.

Para terminar, vamos a hacer que en vez de loguear en la salida estándar, mande el log a un fichero. Como ya hemos dicho, Clojure es 100% java, y la variable especial de salida estándar *out*, no es más que un objeto java.io.OutputStreamWriter, así que vamos a hacer un par de funciones, una para cambiar el destino de logueo almacenado en el estado del agente, y otro para poder iniciarlo con un fichero de salida:

 (defn reset-logger
   "Initializes the logger with a given level and output writer"
   ([level writer]
      (send *logger* (fn [a l w] {:level (level-name-to-int l) :output w}) level writer)))
 
 (defn reset-logger-with-file-output
   "Resets the logger setting the output to the file whose path is provided as an argument
    with the level specified"
   ([level file-path]
      (reset-logger level (new FileWriter (new File file-path) true))))
 

Ya tenemos terminado nuestro logger, de ultimísima tecnología, con el que no tendremos problemas como el que te puedes encontrar en Merb, cuando el tiempo que tarda en mostrar el log de tu aplicación web acaba provocando un timeout por parte del cliente. ;)

Por último, queda comentar sobre los agentes que les ocurre cuando la función de actualización lanza una excepción: intentemos loguear en null, algo que seguro no es una buena idea:

 (send *logger* (fn [_] {:output nil :level 1}))
 (send *logger* (fn [_] {:output nil :level 1}))
 #=(clojure.lang.Agent. "clojure.lang.Agent@219009")
 user=> 
 (log :error "????")
 "????"
 

El mensaje no ha sido loguead, como esperábamos, pero tampoco parece que haya explotado nada, intentemos loguear algo más

 user=> (log :error "are you alive?")
  (log :error "are you alive?")
  java.lang.Exception: Agent has errors (NO_SOURCE_FILE:0)
  user=> 
 

Vaya, parece que nuestro agente se ha quedado en un estado de error. Este es el comportamiento de los agentes Clojure ante los errores, se quedan suspendidos hasta que reseteemos el estado del mismo con la función (clear-agent-errors)

 user=> (clear-agent-errors *logger*)
 (clear-agent-errors *logger*)
 nil
 

¡Listo para usar otra vez!

El código, como siempre, lo podéis descargar de Github

servido por Antonio sin comentarios compártelo

8 Enero 2009

Creando individuos en la ABox: validaciones en una semántica de mundo abierto

Más cosas que voy implementando en mi biblioteca OWL para Clojure.

En otro post, mostré como se podía definir la TBox, con un procedimiento similar al de usar migraciones para definir el esquema de una base de datos relacional. Ahora vamos mostrar lo que sería el equivalente ontológico a insertar filas en las tablas del esquema relacional, es decir a insertar aserciones en la ABox.

Creamos una TBox de prueba:

 (describe-tbox
   (describe-owl-class :test "Person")
   (describe-owl-datatype-property :test "name" (xsd-string))
   (describe-owl-datatype-property :test "age" (xsd-decimal))
   (describe-owl-class-has-property :test "Person" :test "name")
   (describe-owl-class-has-property :test "Person" :test "age"))
 

Una vez guardada la TBox en un repositorio e iniciada (con alias :test-Person, :test-name y :test-age para los recursos), podremos crear individuos en la ABox que sean tipos de las clases OWL contenidas en la TBox, para ello usamos la función (abox-create-individual)

 (abox-create-individual :test-Person
                         "http://test.com/abox/people#"
                         { :test-name "Helena",
                           :test-age 24 })
 

Esta función crea un nuevo individuo con URI "http://test.com/abox/people#IDENTIFIER", donde IDENTIFIER es un identificador universal único (UUID) que se genera con la llamada a la función. La función inserta los tripletes RDF necesarios para asertar que el individuo recién creado es del tipo :test-Person, así como de todas sus superclases (en este caso owl:Thing). Además da valores a las propiedaded test:name y test:age.

Los tripletes RDF equivalentes serían (con notación Turtle):

@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
 @prefix test: <http://test.com/tbox/people#> .
 @prefix owl: <http://www.w3.org/2002/07/owl#> .
 @prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
 
 <"http://test.com/abox/people#IDENTIFIER"> rdf:type test:Person ;
                                            rdf:type owl:Thing ; 
                                            test:age "24"^^xsd:decimal ;
                                            test:name "Helena"^^xsd:string .
 

A la hora de crear individuos en la ABox, hay que tener en cuenta que se pueden crear individuos con valores sólo para algunas propiedades, o con propiedades no definidas en la clase OWL o ni siquiera en la TBox. Una de las características de las lógicas descriptivas, la base formal de OWL, es que tienen una semántica denominada de "mundo abierto" (ver el "Cambridge Handbook of DL"). Siguiendo una semántica "open world", si una información no se ha asertado en la ontología, no quiere decir que no exista, sino que todavía no se conoce. Lo mismo sucede para la información extra. Es interesante comparar esta semántica con una semántica de "mundo cerrado", como la que siguen las bases de datos relacionales, donde si un dato no está en la tabla, significa un error, no desconocimiento. Esta situación se agrava cuando contamos con pocos constructores OWL y no hay soporte para un razonador, si no que se trabaja solo con las aserciones explícitas de la ontología.

Una forma de poner restricciones a lo que insertamos en la ABox, sin recurrir a hacer un test de consistencia con un reasoner tras realizar las aserciones, es permitir añadir validaciones a los recursos descritos en la TBox. Estas validaciones se realizan cuando se quiere hacer una aserción en la ABox para ver si la aserción está permitida o no, controlando qué es válido y qué no es válido asertar. Aún así, esta solución no deja de ser una pequeña chapuza, ya que perdemos la información de las validaciones que no está contenida en la ontología, por lo que siempre que sea posible, se debe modelar correctamente la ontología, insertando la información posible y después añadir las validaciones.

Estas validaciones, son predicados y se pueden realizar en el registro de la TBox. y pueden ir asociadas a cualquier acción y recurso. Por ejemplo:

 (tbox-register-name! :owl-thing (owl-Thing) :connection)
 (tbox-register-name! :test-Person :test "Person"  :connection)
 (tbox-register-name! :test-age :test "age"  :connection)
 (tbox-register-name! :test-name :test "name"  :connection)
 
 (tbox-register-validation-on! :create 
                               :test-age 
                               (fn [age]
                                   (> (:value age) 18)))
 

En este caso, comprobamos que al crear un nuevo individuo que da valor a la propiedad test:age, el valor de la edad es mayor que 18. En este caso la validación es para un propiedad, y se comprobaría cuando se realizase una acción sobre cualquier clase con esa propiedad, de esta manera se pueden reutilizar las validaciones entre diferentes clases. Cuando la validación es a nivel de clase, pero no común para las propiedad compartida entre clases, se puede registrar la validación a nivel de clase:

 (tbox-register-name! :test-AdultPerson :test "AdultPerson" :connection)
 (tbox-register-validation-on! :create
                               :test-AdultPerson
                               (fn [properties] 
                                   (> (:value (:test-age properties)) 18)))
 

Así se comprobaría el predicado cuando se crea un individuo de la clase test:AdultPerson, pero no cuando ce crea cualquier otro individuo de una clase relacionada con la propiedad test:age.

Con las validaciones se pueden ofrecer una semántica para casi todo: propiedades obligatorias, formato correcto de valores ... siempre que no sean excusa para perder información que podría haberse asertado algún constructor de OWL, como allValuesFrom, etc.

Tags: clojure, owl

servido por Antonio sin comentarios compártelo

5 Enero 2009

Una capa de persistencia de datos semántica basada en OWL

Hoy en día la norma para desarrollar la capa de persistencia de una aplicación web consiste en desarrollar un esquema para algún sistema de base de datos relacional en el que almacenar los datos.
Esto supone básicamente darle golpes a tus objetos hasta convertirlos en conjuntos de enteros, cadenas, etc que después se vuelven a encajar en tablas, indicando relaciones entre esas tablas duplicando valores en columnas especiales de esas tablas. Estamos tan acostumbrados a hacerlo de esta manera que ni nos damos cuenta de la gran distancia que media entre nuestro modelo objetual, con el que trabajamos en nuestro código, y el nivel de abstracción mucho más bajo con el que almacenamos una representación persistente de esos datos.

Sin embargo, esto no tendría porque ser así. Cuando creamos y modelamos nuestro dominio de datos para la aplicación, no hacemos más que crear una ontología, una descripción de los recursos del dominio y sus relaciones, y en los últimos años se ha estandarizado un lenguaje para la descripción de ontologías por parte de la W3C: OWL.

OWL nace del mundo de la lógica descriptiva y permite describir las dos partes que componen una ontología:

  • Una TBox: o terminological box, donde describimos una terminología, los tipos de recursos y relaciones, por ejemplo, existen cosas llamadas personas, existe una relación entre una cosa y un número llamada tener edad, etc...
  • Una ABox: o assertional box, donde hacemos aserciones usando los términos de la TBox, por ejemplo: Juan es una persona, Juan tiene una edad de 15, etc...

A continuación mostraré una biblioteca para describir una TBox en Clojure lisp.

Nuestra primera tarea es tener un sitio donde almacenar la TBox, para ello debemos tener una conexión con un repositorio. Con el siguiente código creamos un repositorio en memoria y lo registramos con un nombre para usarlo posteriormente:

  (register-repository! :test (init-memory-repository!))
 

A continuación podemos pasar a describir nuestra TBox para la ontología que vamos a describir, la famosa aplicación para escribir blogs, para ello usamos las funciones (describe-owl-*):

 (write-graph-in-repository
    (describe-tbox
       ;; algunas propiedades de datos
       (describe-owl-datatype-property :superblog "title" (xsd-string))
       (describe-owl-datatype-property :superblog "content" (xsd-string))
       (describe-owl-datatype-property :superblog "name" (xsd-string))
                 
       ;; algunas clases
       (describe-owl-class :superblog "Blog")
       (describe-owl-class :superblog "Post")
       (describe-owl-class :superblog "Author")
                
       ;; asignamos propiedades a clases
       (describe-owl-class-has-property :superblog "Blog" :superblog "title")
       (describe-owl-class-has-property :superblog "Post" :superblog "title")
       (describe-owl-class-has-property :superblog "Post" :superblog "content")
       (describe-owl-class-has-property :superblog "Author" :superblog "name")
 
       ;; algunas propiedades de objetos
       (describe-owl-object-property :superblog "contains")
       (describe-owl-object-property :superblog "writes")
 
       ;; relaciones entre clases
       (describe-owl-class-has-property :superblog "Blog" :superblog "contains")
       (describe-owl-property-range :superblog "contains" :superblog "Post")
       (describe-owl-class-has-property :superblog "Author" :superblog "writes")
       (describe-owl-property-range :superblog "writes" :superblog "Post"))
 
    ;; seleccionamos la conexion con el repositorio anteriormente registrado
    (connection! :test))
 

El código anterior es equivalente a escribir una migración en Ruby on Rails, pero el hecho de usar OWL en vez de directamente una base de datos relacional, nos aporta una serie de beneficios, por ejemplo, la reutilización del esfuerzo empleado en modelar el dominio. En el ejemplo anterior podemos darnos cuenta de que las relaciones como título, ser autor de, etc, ya han sido modelados y estandarizados dentro del Dublin Core Metadata Initiative (http://en.wikipedia.org/wiki/Dublin_Core), ¿por qué no utilizar el soporte para el Dublin Core que incluye la biblioteca? La reutilización se basa en el concepto de kits de vocabularios, que incluyen clases y propiedades y funciones que trabajan sobre estas partículas OWL:

 (use 'com.agh.webserver.framework.persistence.rdf.vocabularies.dc)
 
 ;; cargamos la descripción del Dublin Core en el repositorio de test
 (import-vocabulary-kit :dc (connection! :test)) 
 
 ;; buscamos sinonimos entre nuestro dominio y el DC
 (write-graph-in-repository
    (describe-tbox
       (describe-owl-equivalent-properties :superblog "title" (dc-title))
       (describe-owl-equivalent-properties :superblog "writes" (dc-creator)))
    (connection! :test))
 

La descripción de la TBox de la ontología está en este momento almacenada en el repositorio, para poder usar la TBox de forma eficiente debemos iniciar la TBox al empezar a usar nuestra aplicación declarando que recursos de la ontología almacenada en el repositorio queremos utilizar:

   ;; iniciamos la TBox
  (tbox-register-name! :owl-thing (owl-Thing) :test)
  (tbox-register-name! :superblog-Post "http://superblog.com/Post" :test)
  (tbox-register-name! :superblog-Blog "http://superblog.com/Blog" :test)
  (tbox-register-name! :superblog-Author "http://superblog.com/Author" :test)
  (tbox-register-name! :superblog-content "http://superblog.com/content" :test)
  (tbox-register-name! :superblog-title "http://superblog.com/title" :test)
  (tbox-register-name! :superblog-name "http://superblog.com/name" :test)
  (tbox-register-name! :superblog-post "http://superblog.com/contains" :test)
  (tbox-register-name! :superblog-post "http://superblog.com/writes" :test)
 
  ;; iniciamos el kit de Dublin Core con los nombre por defecto que asigna la 
  :: biblioteca
  (tbox-register-vocabulary-dc :test)
 
   ;; el codigo de la aplicacion empezaria aqui
 

Ahora ya podríamos acceder a los recursos mediante su URI o mediante el nombre con el que se ha registrado. Un test que muestra la interacción entre las diferentes partes, además de la inferencia entre propiedades y subclases, podría ser:

 (deftest test-class-subclassing
   (let [orig-tbox @*tbox*
         orig-conn @*connections*
         orig-repos @*repositories-registry*
         repo (init-memory-repository!)
         graph (describe-tbox
                (describe-owl-datatype-property "http://test.com/prop_a" (xsd-float))
                (describe-owl-datatype-property "http://test.com/prop_b" (xsd-float))
                (describe-owl-class "http://test.com/class_a")
                (describe-owl-class "http://test.com/class_b")
                (describe-owl-class "http://test.com/class_c")
                (describe-owl-subclass "http://test.com/class_a" "http://test.com/class_b")
                (describe-owl-subclass "http://test.com/class_a" "http://test.com/class_c")
                (describe-owl-class-has-property "http://test.com/class_a" "http://test.com/prop_a")
                (describe-owl-class-has-property "http://test.com/class_b" "http://test.com/prop_b"))]
     (do (tbox-clear!)
         (register-repository! :test repo)
         (write-graph-in-repository graph (connection! :test))
         (tbox-register-name! :owl-thing (owl-Thing) :test)
         (tbox-register-name! :class_a "http://test.com/class_a" :test)
         (tbox-register-name! :class_b "http://test.com/class_b" :test)
         (tbox-register-name! :class_c "http://test.com/class_c" :test)
         (tbox-register-name! :prop_a "http://test.com/prop_a" :test)
         (tbox-register-name! :prop_b "http://test.com/prop_b" :test)
         (let [class-recovered (tbox-find-class-by-uri! "http://test.com/class_a")]
           (do 
               (is (= class-recovered
                      {:name :class_a
                       :uri "http://test.com/class_a"
                       :subclass-of [{:name :class_a
                                      :uri {:prefix "", :value "http://www.w3.org/2002/07/owl#Thing"}
                                      :subclass-of []
                                      :datatype-properties #{}
                                      :object-properties #{}
                                      :repository-name :test}
                                     {:name :class_b
                                      :uri {:prefix "", :value "http://test.com/class_b"}
                                      :subclass-of [{:name :owl-Thing
                                                     :uri {:prefix "", :value "http://www.w3.org/2002/07/owl#Thing"}
                                                     :subclass-of []
                                                     :datatype-properties #{}
                                                     :object-properties #{}
                                                     :repository-name :test}]
                                      :datatype-properties #{{:name nil
                                                              :uri
                                                              {:prefix "", :value "http://test.com/prop_b"}
                                                              :range {:prefix "", :value "http://www.w3.org/2001/XMLSchema#float"}
                                                              :equivalent-properties #{}
                                                              :repository-name :test}}
                                      :object-properties #{}
                                      :repository-name :test}
                                     {:name :class_c
                                      :uri {:prefix "", :value "http://test.com/class_c"}
                                      :subclass-of [{:name :owl-Thing
                                                     :uri {:prefix "", :value "http://www.w3.org/2002/07/owl#Thing"}
                                                     :subclass-of []
                                                     :datatype-properties #{}
                                                     :object-properties #{}
                                                     :repository-name :test}]
                                      :datatype-properties #{}
                                      :object-properties #{}
                                      :repository-name :test}]
                       :datatype-properties #{{:name :prop_b
                                               :uri {:prefix "", :value "http://test.com/prop_b"}
                                               :range {:prefix "", :value "http://www.w3.org/2001/XMLSchema#float"}
                                               :equivalent-properties #{}
                                               :repository-name :test}
                                              {:name :prop_a
                                               :uri
                                               {:prefix "", :value "http://test.com/prop_a"}
                                               :range {:prefix "", :value "http://www.w3.org/2001/XMLSchema#float"}
                                               :equivalent-properties #{}
                                               :repository-name :test}}
                       :object-properties #{}
                       :repository-name :test}
               (tbox-restore! orig-tbox)
               (repositories-register-restore! orig-repos)
               (connections-restore! orig-conn))))))
 

Al igual que OWL se situa sobre una pila de protocolos de la W3C para la web semántica, en concreto, una ontología OWL se codifica como un grafo RDF que a su vez se puede describir como un documento XML, la biblioteca OWL usa la bibliteca RDF que comenté anteriormente para describir los recursos en términos de constructores de un grafo RDF y recupera su información transformando ese grafo en consultas SPARQL.

En siguientes posts, veremos como usar esta TBox para hacer aserciones sobre objetos concretos dentro de la ABox.

Aunque todavía se encuentra muy lejos de ser usable para nada, si queréis hacer pruebas y juguetear con él, el código se encuentra disponible en mi repositorio de Github.

Tags: lisp, clojure, owl

servido por Antonio sin comentarios compártelo

1 Enero 2009

Una biblioteca RDF para Clojure

El código y los tests los podéis encontrar en mi repositorio de Github.
Usando Sesame como repositorio semántico. Describimos un grafo RDF:

 (def  *graph*
   (build-graph
     [(build-uri-node "http://test.com/whatever"
         [(build-relation :rdf "relation-1" (build-literal-node "testa1"))
          (build-relation :rdf "relation-2" (build-literal-node "testa2"))])
      (build-uri-node :rdf "test-subjectb"
         [(build-relation :rdf "relation-2" (build-blank-node "a" []))])])
 

El grafo que acabamos de describir es quivalente a los siguientes tripletes RDF (con notación turtle):

@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
 <http://test.com/whatever> rdf:relation-1 "testa1" .
 <http://test.com/whatever> rdf:relation-2 "testa2" .
 rdf:test-subjectb rdf:relation-2 _:a .
 

El grafo que hemos guardado en *graph* podemos manipularlo en memoria, pero para almacenarlo, debemos obtener una conexión con un repositorio. Supongamos que tenemos una base de datos llamada "clojure_sesame", podemos usarla para almacenar el grafo en esa base de datos:

 (def *connection*
      (. (init-repository "com.mysql.jdbc.Driver" 
                          "jdbc:mysql://localhost:3306/clojure_sesame" 
                          "root" 
                          "root")
          getConnection)
 (write-graph-in-repository *graph* *connection*)
 

Una funcionalidad interesante es consultar un grafo RDF de una forma similar al uso que se hace de SQL para consultar un base de datos relacional.
Para dar solución a esta necesidad se ha estandarizado SPARQL (SPARQL Protocol and RDF Query Language) por parte de la W3C (http://www.w3.org/TR/rdf-sparql-query/).

La biblioteca ofrece esta funcionalidad mediante la construcción de plantillas: grafos RDF, donde se pueden insertar constructores para nodos variables y para relaciones variables:

 (def  *template*
   (build-graph-template 
      [{:template (build-graph
                            [(build-variable-node :x
                                 [(build-variable-relation :y 
                                      (build-variable-node :z []))])])
        :filters []}])]
 

La función (build-graph-template) acepta mapas con dos claves, :template que debe contener como valor un grafo con partes variables, y :filters, una colección de filtros que se pueden aplicar a los nodos variables. Por ejemplo:

 (build-filter :x "?x < 25")
 (build-filter :y "isURI(?y)")
 

También se pueden describir partes opcionales de la plantilla con la función (build-optional-graph).

Una vez que tenemos la plantilla debemos seleccionar que variables queremos que la consulta nos devuelva. En el ejemplo anterior, tenemos una plantilla con tres variables :x :y :z, si sólo nos interesan las variables :x e :y, construimos la consulta de la siguiente manera:

 (def *result*
   (query-template-in-repository
                              :x :y
                              *template*
                              *connection*)
 

La invocación anterior es equivalente a realizar la siguiente consulta SPARQL:

 SELECT ?x ?y WHERE { ?x ?y ?z .}
 

Los resultados se devuelven como colecciones de mapas, donde cada mapa contiene una solución para :x e :y:

 ;; El resultado de la consulta
  [{:y {:prefix "", :value "http://www.w3.org/1999/02/22-rdf-syntax-ns#relation-2"}
    :x {:prefix "", :value "http://test.com/test-subjecta/whatever"}}
   {:y {:prefix "", :value "http://www.w3.org/1999/02/22-rdf-syntax-ns#relation-1"}
    :x {:prefix "", :value "http://test.com/test-subjecta/whatever"}}
   {:y {:prefix "", :value "http://www.w3.org/1999/02/22-rdf-syntax-ns#relation-2"}
    :x {:prefix "", :value "http://www.w3.org/1999/02/22-rdf-syntax-ns#test-subjectb"}}]
 

Las plantillas no solo describen patrones para consultas SPARQL, sino que se pueden sustituir las partes variables de la plantilla, si queremos transformarla en un grafo de nuevo. Usando el primer resultado de la consulta, podemos hacer:

 (template-to-graph *template* (first *result*))
 

Y obtendríamos el grafo equivalente resolviendo :x e :y (en este caso todavía quedaría la parte variable referente a :z).

También podemos usar la capacidad de sustitución de las plantillas para dar valores parciales a variables al hacer una consulta:

 (query-template-in-repository
                            :x :y {:z (build-literal-node "testa1")}
                            *template*
                            *connection*)
 

De esta manera obtendríamos una consulta equivalente a:

 SELECT ?x ?y 
 WHERE { 
   ?x ?y "testa1" .
 }
 

Esta biblioteca, ofrece un acceso a RDF de bastante bajo nivel, manipulando tripletes de forma individual, pero puede servir de base para construir una capa de persistencia semántica para aplicaciones web, quizá construida sobre OWL, el lenguaje de descripción de ontologías de la W3C.

Tags: sesame, rdf, clojure

servido por Antonio sin comentarios compártelo

21 Diciembre 2008

Un parser monádico combinatorio en Clojure para enrutar URLs

Una parte básica de cualquier framework web es el router. El componente que se encarga de aceptar una petición desde el servidor web para una determinada URL y método HTTP y decide que código de la aplicación debe ejecutarse para satisfacer dicha petición.

Existen muchas soluciones, algunas más díficiles de usar como el mapeo que se realiza entre URLs y Servlets en una aplicación web Java y otras más elegantes como el router de rails. Últimamente, el router de Rails parece ser el paradigma que imitan la miriada de frameworks web que existen actualmente.

El router de Rails permite escribir código Ruby muy compacto que indica patrones de URL, asignando partes de esas URL a variables y condiciones sobre partes de la URL, así como el controlador y la acción que debe ejecutarse. Por ejemplo:

 map.connect '/activation/:code', 
             :controller => 'users', 
             :action => 'activation', 
             :requirements => { :code => /\d+/}
 

En esa pequeña porción de código Ruby se está indicando que cualquier URL que siga el patrón "/activation/" seguido de lo que sea deberá enrutarse al controlador "users" y a la función "activation", siempre que la parte "lo que sea" de la ruta haga match de la expresión regular especificada. Además la parte "lo que sea" estará disponible para la funcion "activation" como el parámetro "code".

¿Podemos escribir la funcionalidad anterior en Clojure? o mejor aún, ¿podemos mejorarlo? Vamos a ver.

Si observamos desde un punto de vista de alto nivel que es lo que queremos construir nos damos cuenta que nos enfrentamos a un problema de parseo. Supongamos que tenemos un parser que dada una petición HTTP con la siguiente información: método, url, parámetros, lo transforma en una lista de tokens donde se indica el tipo de token (método, parte de la URL, parametros). Un posible parser podría ser el siguiente:

 (defn parse-string-eq
   "Parses a token checking for string equality"
   {:monad :Maybe}
   ([string token state]
      (if (is-token-url-part? token)
        (if (= (token-value token) string)
          (just state)
          (nothing))
        (nothing))))
 

Esta función comprueba que el token que recibe es igual que una cadena que le pasan como parámetro y en función de la comprobación devuelve o error si no hay coincidencia o acepta el tokeny devuelve el estado que le han pasado de parseo, en este caso sin modificar. Se puede observar como la función es monádica. El parseo de un token es un proceso que puede fallar o no, por eso la función envuelve su resultado en la mónada Maybe. Si el parseo falla, devuelve Nothing, si tiene éxito, devuelve Just el estado del proceso de parseo modifcado.

Vamos a probar como funcionaría este parser:

 (deftest test-parse-string-eq-ok
   (is (= (parse-string-eq "test" {:type :url-part :value "test"} {})
          (just {}))))
 

Este test comprueba que el resultado de aplicar el parser para "test" del token "{:type :url-part :value "test"}" y el estado "{}" es correcto "(just {})". Sin embargo, hagamos un test para comprobar el fallo:

 (deftest test-parse-string-eq-nok
   (is (= (parse-string-eq "test" {:type :url-part :value "other"} {})
          (nothing))))
 

En este caso el test falla, no ha coincidencia entre "test" y "other" y el parser devuelve Nothing.


Podemos escribir muchos más parsers, para distintas funciones y tipos de token:

Expresiones regulares:

 (defn parse-regex
   "Parses a token checking for a provided regular expression"
   {:monad :Maybe}
   ([regex token state]
      (if (is-token-url-part? token)
        (if (re-matches regex (token-value token))
          (just state)
          (nothing)))))
 
 ;; test
 (deftest test-parse-regex-ok
   (is (= (parse-regex '#"[a-z]+" {:type :url-part :value "test"} {})
          (just {}))))
 

Métodos HTTP:

 (defn parse-http-method
   "Parses a token looking for a HTTP method"
   {:monad :Maybe}
   ([method token state]
      (if (is-token-http-method? token)
        (if (= (token-value token) method)
          (just state)
          (nothing))
        (nothing))))
 
 ;; test
 
 (deftest test-parse-method-eq-ok
   (is (= (parse-http-method :get {:type :http-method :value :get} {})
          (just {}))))
 

Almacenamiento de partes de la URL en variables:

 (defn parse-variable
   "Captures the token as a variable with the given name"
   {:monad :Maybe}
   ([name token state]
      (let [keyword-name (if (keyword? name) name (keyword name))]
      (if (is-token-url-part? token)
        (just (merge {keyword-name (token-value token)} state))
        (nothing)))))
 
 ;; test
 
 (deftest test-parse-var-ok
   (is (= (parse-variable :test {:type :url-part :value "23"} {:a 1})
          (just {:a 1 :test "23"}))))
 

Y muchos mas...


Como las funciones de parser son monádicas, podríamos combinarlas con el operador monádico de binding (>>=) con el fin de obtener un parser compuesto para una lista de tokens:

 (deftest test-monadic-http-token-composition-ok-1
   (let [request (list '({:value :get, :type :http-method} 
                               {:value "google", :type :url-part} 
                               {:value "es", :type :url-part}) 
                              {:test :a})]
     (is (=
          (do->>= (c_ parse-http-method :get) (just request)
                        (c_ parse-string-eq "google")
                        (c_ parse-variable :lang))
          (just '(nil {:test :a :lang "es"}))))))
 

Como se puede ver hemos utilizado la macro (do->>=) para enlazar tres parsers currificados (c_ es una macro que aplica parcialmente la función) sobre la petición "GET /google.es".


En este punto, tenemos un parser con una notación bastante fea pero que esencialmente tiene las mismas capacidades que el router de Rails. A continucación, necesitamos hacer dos cosas: ofrecer una notación más simple y ofrecer una mayor potencia para parsear URLs.


Empecemos por esto último. Una forma sencilla de hacer parsers más potentes es usar combinadores que enlazan agregan parsers más simples para ofrecer una funcionalidad más compleja. Si conocéis la estupenda biblioteca Parsec de Haskell, sabéis a lo que me estoy refiriendo.

El combinador más sencillo es el combinador "run-one", que acepta un parser, una lista de tokens y un estado de la computación, aplica el parser al primer token y estado, y devuelve o Nothing si el parser falla, o Just el resto de tokens y el estado modificado por el parser, si el parseo tiene éxito:

 (defn run-one
   "Accepts a parser, a stream of tokens and a state
    and runs the the parser on the first token of the
    stream with the given state"
   {:monad :Maybe}
   ([parser computation]
      (let [tokens (first computation)
            state (second computation)] ;; we extract the tokens and state
        (if (nil? tokens) ;; no more tokens?
          (nothing)  ;; error, there should be something more to parse
          (let [parsing-result (parser (first tokens) state)] ;; run the parser
            (if (nothing? parsing-result) ;; parsing error
              (nothing)
              (just (list (rest tokens) ;; on success return rest of tokens and new state
                          (from-maybe parsing-result)))))))))
 

A partir de este combinador, se podrían escribir múltiples combinadores con funcionalidad cada vez más compleja. Por ejemplo:

run-many: que ejecuta el mismo parser sobre tantos tokens de la lista como sea posible:

 (defn run-many
   "Accepts a parser, a stream of tokens and a state
    and runs the parser on every token of the stream
    till the parser fails or there
    are no more tokens left, returning success"
   {:monad :Maybe}
   ([parser computation]
      (loop [current-computation computation] ;; keep on looping till no more tokens or parser error
        (let [tokens (first current-computation)]
          (if (nil? tokens) ;; end of stream?
            (just computation) ;; then success with the current state
            (let [parsing-result (run-one parser current-computation)] ;; run parser one time
              (if (nothing? parsing-result) ;; error?
                (just current-computation) ;; return last successful state and stream
                (recur (from-maybe parsing-result))))))))) ;; let's parse another token
 

run-and: que intenta ejecutar todos los parsers sobre el mismo token o falla

 (defn run-and
   "Accepts a list of parsers and run then sequentially on the
    same token"
   {:monad :Maybe}
   ([parsers computation]
      (let [tokens (first computation)
            state (second computation)]
        (if (nil? tokens)
          (nothing)
          (loop [the-parsers parsers
                 current-state state]
            (let [the-parser (first the-parsers)
                  result (the-parser (first tokens) current-state)]
              (if (nothing? result)
                (nothing)
                (if (nil? (rest the-parsers))
                  (just (list (rest tokens)
                              (from-maybe result)))
                  (recur (rest the-parsers)
                         (from-maybe result))))))))))
 

run-or: que ejecuta una serie de parsers sobre el mismo token hasta que alguno tiene éxito

 (defn run-or
   "Accepts a list of parsers and returns the result of the first
    successful parser or nothing if every parser fails"
   {:monad :Maybe}
   ([parsers computation]
      (let [tokens (first computation)
            state (second computation)]
        (if (nil? tokens)
          (nothing)
          (loop [the-parsers parsers]
            (let [the-parser (first the-parsers)
                  result (the-parser (first tokens) state)]
              (if (nothing? result)
                (if (not (= nil (rest the-parsers)))
                  (recur (rest the-parsers))
                  (nothing))
                (just (list (rest tokens) (from-maybe result))))))))))
 

Las posibilidades son inacabables.

Los combinadores son también funciones monádicas, por lo que podemos enlazarlas también por el operador de binding de mónadas, para construir parsers más sofísticados: Por ejemplo, parsear URLs del tipo (GET || POST) http://(google/)+other/google.es, guardando la última parte como el idioma deseado:

 (deftest test-monadic-http-token-composition-ok-2
   (let [request (list '({:value :get, :type :http-method} 
                               {:value "google", :type :url-part} 
                               {:value "google", :type :url-part}
                               {:value "google", :type :url-part}  
                               {:value "other", :type :url-part} 
                               {:value "google", :type :url-part} 
                               {:value "es", :type :url-part}) 
                              {:test :a})]
     (is (=
          (do->>= (c_ run-or [(c_ parse-http-method :get)
                                         (c_ parse-http-metho :post)]) (just request)
                        (c_ run-many (c_ parse-string-eq "google"))
                        (c_ run-one (c_ parse-string-eq "other"))
                        (c_ run-one (c_ parse-string-eq "google"))
                        (c_ run-and [(c_ parse-string-eq "es")
                                             (c_ parse-variable :lang)]))
           (just '(nil {:test :a :lang "es"}))))))
 

El único problema es que construir el parser combinado, con tantas funciones currificadas y tanto binding, puede ser un poco complicado, así que construiremos una macro que permita especificar parsers de una manera sencilla con unas pocas reglas:

  • una coincidencia literal se expresa como: "parte"
  • una coincidencia con una expresión regular: #"reg-ex"
  • almacenar una parte en una variable: :nombre-variable
  • aplicar or a una serie de parsers: (|| parsers)
  • aplicar and a una serie de parsers: (&& parsers)
  • aplicar many-times a un parser: (* parser)
  • parseo de parámetros: (params parsers)
  • existencia de un parámetro: (params :param-name)
  • existencia y valor de un parámetro: (params (:param-name param-value))
  • método http: METODO

La macro utiliza la función parser-factory, para devolver la función de parseo correcta según el lenguaje de dominio que hemos especificado:

 (defn parser-factory [t]
   "Looks for a correct parser for simplified token notation:
    - \"string\" -> parser-string-eq \"string\"
    - SYMBOL  -> parser-http-method :symbol
    - :keyword -> parser-variable :keyword
    - #\"regex\" -> parser-regex #\"regex\"
    - (* token) -> (run-many token)
    - (&& tokens) -> (run-and (parser tokens))
    - (|| tokens) -> (run-or (parser tokens))
    - (params params) -> parsers combination for params token"
   (if (= (class t) clojure.lang.Symbol)
     (let [kw (keyword (. (str t) toLowerCase))]
     (c_ run-one (c_ parse-http-method kw)))
   (if (= (class t) java.lang.String)
     (c_ run-one (c_ parse-string-eq t))
   (if (= (class t) clojure.lang.Keyword)
     (c_ run-one (c_ parse-variable t))
   (if (= (class t) java.util.regex.Pattern)
     (c_ run-one (c_ parse-regex t))
   (if (= (class t) clojure.lang.PersistentList)
     (let [symb  (first t)
           parsers (rest t)]
       (if (= (str symb) "params")
         (let [others (map (fn[x] (parser-factory-params x)) parsers)]
           (c_ run-and others ))
       (if (= (str symb) "*")
         (let [nt (first parsers)]
               (c_ run-many (parser-factory-inner nt)))
       (if (= (str symb) "&&")
         (let [others (map (fn[x] (parser-factory-inner x)) parsers)]
           (c_ run-and others ))
       (if (= (str symb) "||")
         (let [others (map (fn[x] (parser-factory-inner x)) parsers)]
           (c_ run-or  others))
         (c_ run-one (parse-anything t)))))))
     (c_ run-one (parse-anything t))))))))
 

Con esta macro, ya podríamos reescribir el ejemplo anterior de la siguiente manera:

 (c_ check-route [(|| GET POST) (* "google") "other" "google" (&& "es" :lang)])
 

Además, añadir nuevas funciones de parseo es trivial, simplemente hay que añadir parsers, combinadores y la traducción al DSL para especificar el nuevo parseo en la notación simplificada.

Algún ejemplo más (mock-request construye una lista de tokens de prueba):

 (deftest test-check-route-7
   (is (=
        (check-route '[(|| GET POST) "google" (&& "es" :lang) (params :test (:b 2))] 
             (list (mock-request-http-tokens-with-params :get '("google" "es") '((:test 1) (:b 2))) 
                        {}))
        '{:monad-type :Maybe, :monad-subtype :Just, :content (nil {:lang "es"})})))
 

El código con los tests se puede encontrar en mi repositorio de Github

Tags: clojure, lisp

servido por Antonio sin comentarios compártelo

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 sin comentarios compártelo

10 Diciembre 2008

clojure on grizzly

Clojure (http://clojure.org) es el lenguaje con el que paso más tiempo últimamente.
Es un Lisp-1, funcional (todo es inmutable), ejecutándose sobre la JVM con lo que supone poder acceder a la ingente cantidad de bibliotecas disponibles para Java, con interesantes abstracciones para la programación concurrente, con multimétodos y, por supuesto, con las características tradicionales de Lisp (defmacro!) pero con algunos paréntesis menos.


Por otro lado está grizzly (https://grizzly.dev.java.net/) un proyecto Java que ofrece un framework para la construcción de aplicaciones de red sobre las nuevas bibliotecas Java NIO. Grizzly está integrado en proyectos como Glassfish, donde se encarga de manejar las peticiones HTTP.

¿Podemos usar Clojure para servir aplicaciones web? pues por ahí anda Webjure, un miniframework web escrito en clojure, o se puede invocar a clojure desde un servlet, o implementar el servlet directamente en clojure... las opciones son varias, pero ¿por qué no conectar grizzly con clojure?


El elemento necesario para insertar código clojure en una petición recibida desde grizzly, es una implementación de una clase Adaptader. Como modelo, he modificado el código del adaptador de jruby para grizzly. Este adaptador conecta grizzly con los runtime de jruby configurados e implementa el procotolo Rack (http://rack.rubyforge.org/) que describe una interfaz de comunicación entre servidores web y frameworks de aplicaciones. Así que, siguiendo el ejemplo de jruby, tenemos la clase com.agh.webserver.ClojureAdpater que extiende GrizzlyAdapter
y que implementa el método public void service(GrizzlyRequest req, GrizzlyResponse res) donde se realiza la lógica de proceso de la petición.


Al usar el como base el código de jruby-grizzly, en ese método se construye un nuevo objeto que implementa la interfaz RackApplication que contiene el método RackResponse call(GrizzlyRequest request); como indica la especificación de Rack.
Esta clase hereda de la clase de DefaultRackApplication de jruby-rack con ligeras modificaciones. Esta clase abstracta procesa la petición de grizzly y extrae las variables de entorno que indica la especificación de Rack.


La implementación de esta clase abstracta se solicita desde el adaptador a través de una clase RackApplicationFactory que construye la implementación concreta de DefaultRackApplication para cada framework web. Como nosotros no tenemos framework web, simplemente se ha añadido la clase DefaultRackApplicationFactory que devuelve una subclase anónima de DefaultRackApplication que interpreta la URL de la petición como /directorio/script/función, donde directorio es el namespace de la función y script el nombre del script en ese namespace.

 public RackResponse callMethod(String cmd, final HashMap rackEnv) {
   
   ...
 
    File script = new File(filePath+".clj");
 
      if(script.exists()) {
        try {
          Properties props = System.getProperties();
          RT.loadResourceScript(suffix+".clj");
          RT.loadResourceScript(CLOJURE_RACK);
          Var form = RT.var("com.agh.webserver.rack", "rack-invokation-point");
          PersistentStructMap result = (PersistentStructMap) form.invoke(rackEnv,path[path.length-1]);
 
           ...
 

Como se puede ver, este script llama a la función rack-invokation-point, pasándole como argumentos el nombre de la función a invocar y el entorno de Rack construido. Esta función invoca a su vez a una macro que se encarga de la llamada y del procesamiento de la respuesta:

 (defmacro with-rack-response
   "Embeds a call to a function with a Rack request and response arguments. The
    function should receive two arguments -> the rack request and the rack
    response. Both arguments are references."
   [rack-request invoked-function]
   `(let [req# (ref ~rack-request)
          res# (ref (create-rack-response))]
      (do
        ((find-var (symbol (str "cgi/" ~invoked-function))) req# res#)
        @res#)))
 
 
 (defn rack-invokation-point [req function]
   (with-rack-response req function))
 

La clase Main es el punto de entrada de la aplicación que fija el contexto, el document root de la misma, el puerto de escucha, etc. a través de una subclase de SelectorThread, en nuestro caso el ClojureSelectorThread

 ClojureSelectorThread selectorThread = new ClojureSelectorThread();
 selectorThread.setRailsRoot("/Users/antonio/Desktop/clj-web-app/");
 selectorThread.setPort(8080);
 selectorThread.setAdapter(new ClojureAdapter("/", new ClojureRuntimeAsyncFilter()));
 try {
   selectorThread.initEndpoint();
   selectorThread.startEndpoint();
 } catch (IOException ex) {
    System.out.println(ex.getMessage());
 } catch (InstantiationException ex) {
     System.out.println(ex.getMessage());
 }
 

Ahora solo queda crear nuestra función clojure para ser invocada a través de grizzly, esta estará situada en el path cgi/cgi.clj a partir del directorio que hayamos indicado como document root para la aplicación en la clase Main. Por ejemplo:

 (ns cgi)
 
 (use 'com.agh.webserver.rack)
 
 (defn hello-complex [request resp]
   (render resp
           "<h1> Hola Grizzly!!</h1>"
           200
           {:Content-type "text/html"}))
 

Lanzamos nuestro servidor, y ya podemos probar a invocar nuestro script.

Aquí tenéis la URL del código en github por si queréis echarle un vistazo:

http://github.com/antoniogarrote/clojure-grizzly-trial/tree/master

Tags: clojure, grizzly, lisp

servido por Antonio sin comentarios compártelo


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