October 9, 2021
By: Jesús Gómez

Clojure el Jar

Los clojuristas estamos acostumbrados a interactuar con Clojure con las órdenes clojure o clj.

Lo que para muchos puede ser algo desconocido es el hecho de que esos comandos son en realidad unos «scripts» escritos en Bash, conocidos como «Clojure CLI», y que en realidad, Clojure es un programa distribuido como un .jar.

Esta información es particularmente útil para entender por qué podemos determinar la versión específica de Clojure como una dependencia de nuestros proyectos de la misma manera que determinamos cualquier otro paquete de terceros.

Esta es una de las flexibilidades de Clojure que pasa desapercibido.

Si bien en otros lenguajes hay maneras para definir explícitamente en nuestros proyectos qué versión de dichos lenguajes utilizar, típicamente se hace de manera diferente a la que definimos las versiones de los paquetes de terceros.

En este artículo quiero demostrar cómo trabajar con Clojure sin la ayuda de «Clojure CLI».

¿Cómo obtener el jar?

Antes de empezar, debo explicar cómo obtener el paquete .jar.

Recuerda que utilizar el .jar directamente no es la forma cotidiana de utilizar Clojure, y por ende, no existe una manera trivial de obtenerlo.

Una manera de hacerlo es seguir las instrucciones de la guía de iniciación («Getting Started») en la sección «Otras maneras de correr Clojure» («Other ways to run Clojure»).

La idea es clonar el repositorio de Clojure, y ejecutar la tarea de empaquetado.

En otras palabras:

$ git clone https://github.com/clojure/clojure.git
$ cd clojure
$ mvn -Plocal -Dmaven.test.skip=true package

El paquete clojure.jar generado tendrá todas las dependencias necesarias para soportar todo el lenguaje.

Ejecutar Clojure

Al ejecutar el .jar sin ningún argumento, una sesión interactiva con el "REPL" inicia:

$ java -jar clojure.jar
Clojure 1.11.0-master-SNAPSHOT
user=> (+ 1 1)
2

Otra forma de interesante de invocar Clojure es incluyendo el paquete clojure.jar explícitamente en el "Class Path" y ejecutar su "clase principal".

$ java -cp clojure.jar clojure.main
Clojure 1.11.0-master-SNAPSHOT
user=> 

¿Cómo supe que la clase principal era clojure.main?

Al pasar un «script» como argumento, Clojure lo interpreta expresión por expresión (evita la sesión interactiva con el "REPL").

Supongamos que tenemos un archivo llamado hola.clj con el siguiente contenido:

(println "Hola Infinito")

Entonces la siguiente orden mostrará el Mensaje "Hola Infinito":

$ java -jar clojure.jar hola.clj
Hola Infinito
$ 

Si en vez del nombre del «script», colocas -, entonces Clojure interpreta la entrada estándar como si fuera dicho «script».

$ echo '(println "Hola Infinito")' | java -jar clojure.jar -
Hola Infinito

La opción -e hace que Clojure evalúe la expresión determinada.

$ java -jar clojure.jar -e '"Hola Infinito"'
Hola Infinito

En este caso, no hizo falta utilizar la función println, porque -e no sólo evalúa la expresión dada, sino que su resultado lo coloca en la salida estándar.

Las comillas simples son para indicarle al «shell» que no trate de interpretar nada del contenido que encierran. De esta manera, podemos escribir las comillas dobles (") con la seguridad de que serán recibidas por Clojure, en cuyo momento, representan el inicio y el final de una cadena de caracteres.

Puedes pedirle a Clojure que evalúe varias expresiones a la vez. Producirá una línea por valor en la salida estándar:

$ java -jar clojure.jar -e 1 -e 2 -e 3
1
2
3

Con la opción -m, Clojure llama la función -main del espacio de nombre que especifiquemos.

Supongamos un archivo llamado hola.clj en la carpeta actual, con este contenido:

(ns hola)

(defn -main [& args]
  (println "Hola Infinito"))

Entonces, esa función -main la podemos ejecutar de la siguiente manera:

$ java -cp ".:clojure.jar" clojure.main -m hola

Nota que utilizamos la forma con el -cp en vez del -jar. Esto es debido a que debemos colocar en el "Class Path" el directorio actual (.) para que Clojure pueda conseguir nuestro archivo hola.clj.

¿Por qué hicimos esto?

Lo principal es entender que Clojure es simplemente un paquete como cualquier otro y que las órdenes clojure y clj son herramientas para trabajar con dicho paquete.

Entender esta diferencia nos puede ahorrar muchas confusiones.

En particular, a la hora de conversar, es importante distinguir de qué estamos hablando: Clojure o Clojure CLI.

También nos permite utilizar cualquier conocimiento general del manejo de archivos .jar para manipular clojure.jar, y estar claro en cuál es su posición en el ecosistema Java.

Finalmente quise explorar qué podemos hacer con el paquete clojure.jar independientemente de las herramientas. Porque me parece que entender las funciones básicas de cualquier programa es vital para aprovecharlo al máximo.

En un siguiente artículo exploraré Clojure CLI enfocándome en los detalles donde su relación con Java se hace necesariamente explícita.

Apéndice

Determinar el punto de entrada de clojure.jar

La clase principal de un paquete ".jar" está definida en su manifiesto (META-INF/MANIFEST.MF).

$ jar xf clojure.jar META-INF/MANIFEST.MF
$ grep Main-Class META-INF/MANIFEST.MF 
Main-Class: clojure.main
Tags: Clojure jar es Java