Clojure
Clojure es un lenguaje de programación de propósito
general dialecto de Lisp. Hace un énfasis especial en el paradigma funcional,
con el objetivo (entre otros) de eliminar la complejidad asociada a la
programación concurrente. Clojure puede ser ejecutado sobre la Máquina Virtual
de Java y la máquina virtual de la plataforma .NET, así como compilado a
JavaScript.
Principios
Rich Hickey describe el desarrollo de Clojure como la
búsqueda de un lenguaje que no pudo encontrar: un lisp funcional por defecto,
integrado sobre un entorno robusto en lugar de ser su propia plataforma, y con
la programación concurrente en mente.1
Asimismo, en principio se rechaza la orientación a
objetos, ofreciendo un enfoque en el que los programas se expresan como la
aplicación de funciones sobre datos, más que como la interacción entre
entidades mutables que mezclan representación de datos, y comportamiento. Por
otra parte, características tales como instanciabilidad, polimorfismo e
interfaces son efectivamente parte del lenguaje.
Sintaxis
Como el resto de la familia Lisp, la sintaxis de
Clojure está construida sobre expresiones simbólicas que son convertidas en
estructuras de datos por un lector antes de ser compiladas. Las expresiones se
caracterizan por estar delimitadas por paréntesis, y por su notación prefija,
por la que se llama al primer miembro de cada lista como función, pasándole el
resto de miembros como argumentos.
Esta peculiaridad, extraña para los habituados a los
lenguajes más populares basados en la sintaxis del lenguaje de programación C
es la base de su flexibilidad. Estructuras de datos tales como mapas, conjuntos
y vectores tienen una expresión literal; no requieren transformación alguna a
la hora de incorporarse al árbol sintáctico generado por el compilador. Clojure
es un Lisp-1 y no está particularmente diseñado para ser compatible con otros
lisps.
Macros
Un macro es un fragmento de código que acepta como
argumentos otras expresiones, sin evaluar, transformándolas previamente a su
evaluación. Esto permite la aparición de "programas que crean
programas": incorporaciones al lenguaje -estructuras de control de flujo-
o creación de un lenguaje específico del dominio. Posibilidades en principio no
alcanzables en otras familias de lenguajes sin recurrir al desarrollo de un
compilador.
El sistema de macros de Clojure es muy similar al de
Common Lisp con la excepción de que la versión de Clojure de la comilla inversa
(llamada "comilla sintáctica") cualifica los símbolos con el espacio
de nombres al que pertenece. Así se ayuda a prevenir la captura no intencionada
ya que están prohibidos los bindings con nombres cualificados. Es posible
forzar la expansión de una macro que las capture pero debe hacerse
explícitamente. Clojure prohíbe también re-enlazar nombres globales en otros
espacios de nombres que hayan sido importados en el actual.
Otra característica de la comilla sintáctica es que
permite un sistema de templating, en el que se puede especificar qué miembros
de una lista deben evaluarse mediante los operadores unquote (~) y
unquote-splice (~@), dando a lugar a macros más concisas y manejables.
Características del lenguaje
Desarrollo dinámico con una consola de evaluación (en
inglés, REPL: read eval print loop).
Representación de funciones como valores, y
preferencia por la recursión y el uso de higher order functions sobre de la
iteración basada en efectos secundarios.
Números de precisión arbitraria, y representación
literal de fracciones, generadas en las divisiones no enteras.
Secuencias con evaluación perezosa (los elementos de
la secuencia no se computan hasta que son necesarios, lo que permite
representar conjuntos infinitos en potencia).
Sistema integrado de estructuras de datos persistentes
e inmutables.
Control del estado (conjunto de valores que puede
adquirir una entidad en el tiempo) en situaciones de concurrencia a través de
sistemas transaccionales, de agentes y mediante bindings locales.
Interacción con java: al compilarse a bytecode de la
JVM, las aplicaciones escritas en Clojure pueden ser fácilmente integradas en
servidores de aplicaciones u otros entornos Java con escasa complejidad
adicional. Se implementan por defecto todas las interfaces posibles a nivel de
clases, estructuras de datos y concurrencia para minimizar los esfuerzos
requeridos para conseguir esta portabilidad.
Ejemplos
Hola mundo. Nótese que dada la naturaleza del REPL, no
es necesaria una orden de impresión.
"Hola, mundo!"
Un generador de números únicos y consecutivos que
soporta llamadas concurrentes:
(let [i
(atom 0)]
(defn generar-id-unica
"Devuelve un
identificador numérico distinto para cada llamada."
[]
(swap! i
inc)))
Una subclase anónima de java.io.Writer que no escribe
en ningún sitio y una macro que lo usa para silenciar todas las expresiones
evaluadas con ella.
(def
bit-bucket-writer
(proxy [java.io.Writer] []
(write [buf] nil)
(close []
nil)
(flush [] nil)))
(defmacro noprint
"Evalua
la expresiones dadas con todas las impresiones a *out* silenciadas."
[& forms]
`(binding [*out* bit-bucket-writer]
~@forms))
(noprint
(println "Hello, nobody!"))
En este ejemplo diez hilos manipulan una estructura de
datos compartida, que consiste en cien vectores que contienen diez números
únicos al inicio secuenciales. Cada hilo elige dos posiciones aleatorias en dos
vectores aleatorios y los intercambia. Todos los cambios en los vectores se
hacen dentro de transacciones usando el sistema de memoria transaccional por
software de Clojure. Es por eso que incluso después de mil iteraciones no se
pierde ningún número.
(defn run
[nvecs nitems nthreads niters]
(let [vec-refs (vec (map (comp ref vec)
(partition nitems
(range (* nvecs nitems)))))
swap #(let [v1 (rand-int nvecs)
v2 (rand-int nvecs)
i1 (rand-int nitems)
i2 (rand-int nitems)]
(dosync
(let [temp (nth @(vec-refs v1)
i1)]
(alter (vec-refs v1) assoc
i1 (nth @(vec-refs v2) i2))
(alter (vec-refs v2) assoc
i2 temp))))
report #(do
(prn (map deref vec-refs))
(println "Distinct:"
(count (distinct
(apply concat (map deref vec-refs))))))]
(report)
(dorun (apply pcalls (repeat nthreads
#(dotimes [_ niters] (swap)))))
(report)))
(run 100 10 10 100000)
Salida del ejemplo anterior:
([0 1 2 3 4 5 6 7 8 9] [10 11 12 13 14 15 16 17 18 19]
...
[990 991 992
993 994 995 996 997 998 999])
Distinct: 1000
([382 318 466 963 619 22 21 273 45 596] [808 639 804
471 394 904 952 75 289 778] ...
[484 216 622
139 651 592 379 228 242 355])
Distinct: 1000
Comentarios
Publicar un comentario