Introducción a Clojure en 5 minutos

Después de explicar cómo montar el entorno para desarrollar con Clojure tenía pensado escribir un caso práctico de uso de Clojure, pero teniendo en cuenta lo críptica que es la sintaxis para los que venimos de C# y otros lenguajes orientados a objetos, creo que será mejor empezar por una mínima introducción a la sintaxis de Clojure antes de intentar hacer nada útil.

OJO: No soy un experto (ni mucho menos) en Clojure, así que es más que probable que parte de lo que cuente aquí sea incompleto, no esté bien explicado o sea directamente falso. Cualquier corrección será bienvenida. Es fácil encontrar tutoriales de Clojure escritos por gente que sabe mucho más que yo y que cubren el lenguaje en mayor profundidad.

Todos los ejemplos que aparecen en este post se pueden ejecutar usando el REPL de leiningen que instalamos en el post anterior.

Listas y vectores

Como ya dije en el post anterior, Clojure es un dialecto de Lisp, que es un acrónimo de LISt Processing. Eso quiere decir que el elemento principal con el que vamos a trabajar son listas. Las listas son la construcción básica de Clojure y permiten invocar funciones.

Una lista se representa como un conjunto de elementos entre paréntesis:

(max 8 3)

Para evaluar una lista el primer elemento se considera una función y se invoca usando el resto de elementos como parámetros. En el ejemplo anterior, se invocaría la función max con los argumentos 8 y 3 dando como resultado (obviamente) 8.

Los elementos de la lista pueden ser cualquier cosa, incluyendo otras listas, lo que permite crear expresiones más complejas:

(max 8 (* 3 3))
; => 9

Si lo piensas un poco, las listas son una representación directa del árbol de sintaxis abstracta del código fuente.

Además de listas, en Clojure existen muchas otras estructuras de datos, pero por ahora nos vamos a quedar con una de ellas, el vector. Un vector puede contener cualquier número de elementos y se representa indicando los elementos entre corchetes:

[1 "Gato" 19.3]

A diferencia de las listas, un vector no se evalúa considerando el primer elemento una función, sino que se evalúa cada elemento por separado para obtener el vector resultado:

[1 "Gato" (* 4 3)]
; => [1 "Gato" 12]

Variables y funciones

Para poder hacer cosas interesantes vamos a necesitar un par de conceptos más: variables y funciones.

En Clojure podemos definir una variable usando la forma especial def. La defición de «forma especial» queda un poco lejos del alcance de este post, pero puedes aprender sobre ellas en las reglas de evaluación de Clojure.

Volviendo a lo que nos ocupa, podemos usar def para definir una variable:

(def two 2)
(def one 1)

(+ one two one two)
; => 6

Las variables que definimos con def tienen un valor inicial asociado que se conoce como root binding, pero ese binding puede ser redefinido de forma local para cada hebra (algo parecido al [ThreadStatic] de C#).

Para definir una función se debe usar la forma especial fn, que recibe como parámetros el nombre de la función (opcional), un vector con los parámetros de la función y una lista con el cuerpo de la función. Podemos usar fn para definir funciones «al vuelo», al estilo de las funciones autoinvocadas de javascript:

((fn square [x] (* x x)) 10)
; => 100

En Clojure, como en todo lenguaje funcional, las funciones son ciudadanos de primera clase, es decir, se pueden asignar a variables, pasar como parámetros a otras funciones y, en general, hacer cualquier cosa horrible que se te ocurra con ellas. Si queremos invocar la función más de una vez (algo bastante frecuente), en lugar de declararla e invocarla en una única expresión como hemos hecho antes, podemos asignarla a una variable:

(def square (fn [x] (* x x)))

(square 10)
; => 100

Como esto de definir funciones para reutilizarlas es algo habitual, existe una macro que nos permite hacerlo de forma más corta:

(defn square [x]
    (* x x))

(square 10)
; => 100

Este ejemplo es equivalente al anterior (def square (fn...)), pero al utilizar la macro defn nos ahorramos un poco de código.

Las macros nos permiten «aumentar» el lenguaje con nueva sintaxis, y esa es una característica muy interesante de Clojure. En lugar de definir una sintaxis muy completa con un montón de formas especiales, el lenguaje trata de ser lo más simple posible y de formar las construcciones más complejas a partir de las más simples, sin necesidad de introducir nuevos elementos en el lenguaje.

Resumen

Lo que hemos visto en este post es una mínima parte de Clojure, pero nos servirá para poder empezar a leer código (que ya sabemos que es lo que nos gusta) e ir profundizando poco a poco en más características de este lenguaje tan peculiar.

Pese a lo básica que es esta introducción, hemos visto dos cosas que (creo) son importantes en la filosofía de Clojure.

Por una parte, construir todo a base de listas hace representemos el código como una estructura de datos más del lenguaje. Sería parecido a ir construyendo el código a base de Expression<T> en C#.

Gracias a eso, el lenguaje se puede basar en un conjunto pequeño de primitivas y macros para definir dinámicamente nueva sintaxis que nos permita expresar operaciones más complejas.

Con esto ya estamos listos para empezar a algo remotamente útil con Clojure en futuros posts.

2 comentarios en “Introducción a Clojure en 5 minutos

Comentarios cerrados.