Publicado en
Continuando con esta serie de lenguajes para la JVM, en esta ocasión les presentamos Clojure. Clojure (pronunciado “cloushur”) es un dialecto del lenguaje de programación Lisp que utiliza el estilo de programación funcional y es muy adecuado para construir sistemas de procesamiento concurrente (multithreaded). Clojure puede ejecutarse tanto en la máquina virtual de Java (JVM) como en el CLR de .Net, e incluso se puede compilar hacia Javascript.
En este artículo jugaremos un poco con Clojure para conocerlo.
Habilitando el ambiente
Lo primero que necesitamos es tener instalado un ambiente de ejecución de Java (JRE). Una vez teniendo esto, la forma más sencilla para habilitar un ambiente de Clojure es utilizando Leiningen, la herramienta preferida para construcción de proyectos Clojure.
Leiningen se puede obtener en https://github.com/technomancy/leiningen. En Linux y OSX, su instalación básicamente consiste en descargar el script lein, ponerlo en un directorio de ejecutables (ej: /usr/bin), darle permisos de ejecución y correrlo. Es decir:
sudo wget http://bit.ly/leinscript -O /bin/lein
sudo chmod +x /bin/lein
La primera vez que ejecutemos ‘lein’ instalará las dependencias que necesita. Si utilizas Windows, consulta las instrucciones de instalación en la página github de Leiningen.
Teniendo instalado el script lein, podemos utilizar REPL, que es una consola para Clojure. Sé que REPL suena un poco raro, pero son las siglas de “read eval print loop”. Para iniciar REPL tecleamos:
lein repl
Al hacer esto, en la terminal nos debe quedarel prompt ‘user=>’. De aquí en adelante, todos los textos en este tutorial que estén precedidospor ‘=>’ se refieren a instrucciones que deben alimentarse en REPL.
Comencemos
Comencemos introduciendo algunos números y viendo si Clojure los puede interpretar.
=>3
=>2/3
=>-5
Ahora vectores:
=>[1 2 2.9]
=>[1 “hola”]
=>[];vector vacío
Como puedes ver, todo lo que viene después de un ; es un comentario.
Ahora hagamos unas listas:
=>();lista vacía
=>(1 2 3);oooops?
Esa última instrucción debe haber generado una excepción. Antes de explicar la razón de ésta, notemos que es una excepción java.lang, lo cual nos recuerda que en el fondo estamos corriendo Clojure sobre Java. ¿Y por qué salió la excepción? Bueno,recordemos que Clojure es un dialecto de Lisp (LISt Processing) así que su paradigma consiste en evaluar listas, y el primer elemento de una lista debe ser una función y el resto de los elementos son los argumentos para dicha función. Entonces, al recibir (1 2 3) Clojure trata de interpretar 1 como función, lo cual no es correcto.
Funciones
Ejecutemos nuestras primeras funciones:
=>(= 1 2)
=>(str “hola mun” 2)
=>(map str [1 2 3])
Ahora intentemos lo siguiente:
=>(def tres 3)
=>tres
Estamos definiendo el símbolo ‘tres’ y asignándolo al objeto 3. Bajo la misma lógica, ahora intentemos:
=>(def mastres ( fn [x] (+ x tres)))
=>(mastres 1997)
Felicidades, hemos creado nuestra primer función en Clojure. El macro ‘defn’ nos provee una sintaxis un poco más sencilla para definir funciones. Podemos volver a definir nuestra función como:
=>(defn mastres [x] (+ x tres))
Map y Reduce
Una función bastante útil es ‘map’, la cual aplica una función a todos los elementos de una colección, tal como una lista, vector o mapa.
=>(map #(+ 3 %) [1 2 3 4 5])
¿Qué fue eso de #(+ 3 %)? Es una sintaxis más corta para escribir funciones anónimas, equivalente a (fn [x] (+ x 3)). Con esta sintaxis, cuando quieres agregar más de un argumento lo haces de esta forma: #(* %1 %2)
Al definir esta función no utilizamos def o defn para ponerle un nombre porque, como ya se comentó, es una función anónima. Las funciones anónimas son muy útiles principalmente al trabajar con funciones de alto orden, es decir, con funciones que tienen como algunos de sus argumentos otra función. Tal es el caso de ‘map’, en donde su primer argumento es la función que le vas a aplicar a la colección que metes como segundo argumento.
Otra función que utilizaremos mucho es ‘reduce’, la cual nos permite iterar aplicando una función a una lista de elementos. Para ejemplificar esto regresemos a nuestra función ‘mastres’, si quisiéramos que la aridad (es decir, el número de argumentos que la función acepta) de nuestra función fuera infinita haríamos lo siguiente:
=>(defn mastres ([x] (+ x 3)) ([x y] (+ x y 3)) ([x y & otrosargs] (+ x y 3 (reduce + otrosargs))))
Noten que estamos usando un ‘&’. Cuando usas el & en el vector de parámetros, todos los parámetros que agregues al ejecutar la función los convierte en una lista. Y luego estamos utilizando esa lista a reduce para que itere a través de ella, sumando sus elementos hasta que se reduzca a un solo elemento.
Documentación como función
La documentación de los elementos de Clojure está disponible por medio de la función ‘doc’. Asímismo, puedes consultar el código fuente de cada uno con la función ‘source’.
=>(doc doc)
=>(doc source)
=>(doc reduce)
Estructuras de datos
Clojure se basa en los tipos de datos básicos de Java. Por ejemplo, los números derivan de java.lang.Number y los strings de java.lang.String. Clojure soporta colecciones tales como listas, mapas, vectores y conjuntos. Para mayor detalle, consulta la documentación de Clojure sobre estructuras de datos [4].
Continuemos con algunos ejemplos. Te recomiendo usar doc y source para entender lo que está pasando.
=>(if (coll? ()) “la lista vacia es una coll” “la lista vacia no es una coll”)
=>(str [1 2 3])
=>(apply str [1 2 3])
=>(re-seq #”[^ ]+” “Hola chicas”) ;expresiones regulares
Una propiedad interesante de Clojure es que cuenta con estructuras de datos “flojas” (lazy). Esto significa que Clojure no va a hacer muchos procesamientos hasta que pides el resultado. Por ejemplo, la función ‘range’ provee una secuencia de números enteros. Si la utilizas con 1 argumento, te regresa la lista de números desde el 0 hasta llegar al número indicado (no inclusivo). Si la utilizas con 2 argumentos, utiliza el primero como valor de inicio y el segundo como final. Pero si no utilizas argumentos, se irá hasta el infinito y no tendrás otra opción mas que matar tu proceso o cerrar tu terminal.
Namespaces
Durante estos ejercicios hemos tenido el prompt ‘user=>’ así que hemos estado todo este tiempo habitando en el namespace ‘user’. Intentémos invocar un comando de shell, por ejemplo “ls” para que nos de el listado de archivos del directorio que estamos.
=>(sh “ls”)
Seguramente recibiste una excepción porque REPL no entendió a qué te refieres con “sh”. La función sh se encuentra en el namespace clojure.java.shell. Así que para usarlo ponemos:
=>(use ‘clojure.java.shell)
=>(sh “ls”)
Ahora sí funcionó. Como se habrán dado cuenta, nos regresa un mapa de llaves y valores. Si queremos sólamente obtener el valor de “out”, hacemos:
=>(:out (sh “ls”))
Conclusión
Bien, ya hemos dado los primeros pasos para comprender la esencia de Clojure. Te invito a que conozcas más sobre este interesante y útil lenguaje. La lista de referencias incluyo los principales sitios con documentación y tutoriales de Clojure. Adicionalmente, puedes enviar un mensaje a la línea de dudas en español vía twitter en @clojure_mexico. Para despedirme, los invito a que creen su primer proyecto de Clojure tecleando lo siguiente desde la línea de comando:
lein new proyecto
Referencias
[1] http://learn-clojure.com
[2] http://clojure.org
[3] http://clojure.github.com/clojure
[4] http://clojure.org/data_structures
Andrés Gómez coordina la comunidad de @clojure_mexico y es director de tecnología en Fractal Media, en donde se utiliza clojure para crear soluciones inteligentes para marketing digital. @fractalresearch
- Log in to post comments