Apuntes
Lenguaje de programación
Familia: LISP (Common Lisp y Scheme)
Fuente: http://lisperati.com
Fuente: http://landoflisp.com
Homoicónico: su sintaxis se basa en la escritura de listas:
- Lista como código fuente:
(* 3 (count '(10 20 30 40 50)))
- Lista como colección:
'(10 20 30 40 50)
,(quote (10 20 30 40 50))
o(list 10 20 30 40 50)
- Lista como código fuente:
- Interpretado: el código fuente es dado a un lector (Reader) como una secuencia de caracteres,
quien crea estructuras de datos a partir de las expresiones encontradas. Las estructuras de datos creadas por
el lector son entregadas al evaluador (Evaluator) quien resuelve o evalua las expresiones e imprime (Print)
el resultado de evaluar las expresiones.
Fuente: Clojure for Java Programmers por Rich Hickey - Evaluación de expresiones: en el código fuente se establecen expresiones las cuales
son evaluadas o resueltas estrictamente. Las expresiones se escriben mediante formas:
- Formas normales: su evaluación es literal.
3.1416
"¡Hola mundo!"
{:lenguage "Clojure" :paradigma "Funcional"}
- Formas compuestas: están conformadas por formas normales, las cuales deben ser evaluadas
previamente.
(str "¡Hola" " " "mundo!")
(count [[true false] [false true])
(+ (* 2 3) (- 5 1) (/ 10 5))
- Formas especiales: están conformadas por un símbolo y formas compuestas. El símbolo establece
ciertas reglas de evaluación de las formas compuestas.
- La forma especial
if
:(if (> (* 2 3) (+ 3 3)) [10 20 30] [40 50 60])
- La forma especial
.
(dot):(.toUpperCase "!hola mundo!")
- La forma especial
- Formas constantes: evitan su evaluación.
'x
'(10 20 30 40 50)
(quote [+ * -])
- Macro: una forma especial que permite establecer nuevas abstracciones (de sintaxis o semántica)
dentro del mismo lenguage de programación.
- La macro
let
:(let [x 10 y 20] (+ (* 2 x) (* 2 y)))
- La macro
and
:(and true true)
- La macro
fn
:(fn [x] x)
- La macro
- Formas normales: su evaluación es literal.
-
Los tipos de datos son identificados en tiempo de ejecución (evaluación o resolución).
type
es una función que regresa el tipo de dato del argumento dado mientras quefirst
es una función que regresa el primer elemento de la secuencia dada yfn
es una macro para la definición de funciones:=> (type 456) java.lang.Long => (type \♞) java.lang.Character => (type false) java.lang.Boolean => (type "¡Hola mundo!") java.lang.String => (type [10 20 30 40 50]) clojure.lang.PersistentVector => (type (/ 10 3)) clojure.lang.Ratio => (type (/ 10 3.0)) java.lang.Double => (type ((fn [x] (first x)) "¡Hola mundo!")) java.lang.Character => (type ((fn [x] (first x)) [10 20 30 40 50])) java.lang.Long => (type (fn [x] x)) $eval8009$fn__8010
En Clojure un tipo se puede entender como una etiqueta de lo que es una expresión, sin restringir o forzar lo que está permitido hacer con la expresión. Lo anterior permite escribir expresiones que en cierto contexto no tengan sentido o impriman errores al ser evaluadas. Por ejemplo:
=> (type (fn [x] (* 2 x))) clojure02.core$eval8126$fn__8127
Dicha expresión ha sido etiquetada como una función:
=> ((fn [x] (* 2 x)) 6) 12 => ((fn [x] (* 2 x)) 10) 20 => ((fn [x] (* 2 x)) 14) 28
Sin embargo no se restringe su correcto uso:
=> (+ 10 20 30) 60 => (+ 10 20 (fn [x] (* 2 x))) ClassCastException clojure02.core$eval8230$fn__8231 cannot be cast to java.lang.Number ...
De igual manera no se restringe lo que sea
x
dentro de dicha función:=> ((fn [x] (* 2 x)) "¡Hola mundo!") ClassCastException clojure.lang.String cannot be cast to java.lang.Number clojure.lang.Numbers.multiply ...
Sin embargo, puesto que las funciones no quedan restringidas a cierto tipo de dato en sus parámetros se pueden generalizar ciertas operaciones o ciertos algoritmos:
=> ((fn [xs] (list (first xs) (last xs))) "¡Hola mundo!") (\¡ \!) => ((fn [xs] (list (first xs) (last xs))) [10 20 30 40 50]) (10 50) => ((fn [x xs] (str x xs)) \e "fghij") efghij => ((fn [x xs] (str x xs)) "abcde" "fghij") abcdefghij
Funcional: las funciones son ciudadanos de primera clase por lo que pueden ser usados como parámetros o valores regresados por otras funciones:
(apply max [5 3 7 2 4 1])
(map (fn [x] [x x]) [10 20 30])
(reduce (fn [v c] (conj v [(count v) c])) [] "HOLA")
Formas
Comentarios:
El carácter
;
inicia un comentario hasta el fin de la línea en que se encuentra.=> ; Comentario fuera de alguna expresión ((fn [xs] ; Comentario dentro de alguna expresión (first xs)) ; Otro comentario ; seguido de otro comentario [true false true]) true
La macro
comment
evita la evaluación de las formas que se encuentran en su interior, regresandonil
.=> (comment ((fn [xs] (first xs)) [true false true])) nil
Literales:
- Números:
456789
,-345
,10/3
,45.24
. - Booleanos:
true
,false
. - Caracteres:
\@
,\ñ
,\Z
,\\
,\5
,\$
,\space
,\tab
,\newline
,\♛
,\u265b
. - Cadena de caracteres (string):
""
," "
,"5"
,"áéíóúÁÉÍÓÚàêĩöü"
,"♔♕♖♗♘♙♚♛♜♝♞♟"
,"¡Hola mundo!"
. - Keywords:
:edad
,:nacionalidad
,:paradigma
. Vacío o nada:
nil
.En un contexto booleano es evaluado como
false
:=> (not nil) true => (if nil (* 2 10) (* 3 10)) 30
Colecciones: listas, vectores, conjuntos y mapas.
Símbolos
Nombres dado a valores:
x
,y
,altura
,base
,+
,apply
,max
, etc.:=> (type +) clojure.core$_PLUS_ => (type '+) clojure.lang.Symbol
Colecciones
"Clojure: The Art of Abstraction" por Alex Miller.
Las colecciones son abstraidas bajo las ideas de secuencia y asociación.
Secuencia: elementos continuos uno de otro:
- listas: sus elementos son continuos uno de otro por lo que mantienen un orden, permitiendo su repetición.
- vectores: sus elementos son continuos uno de otro mediante un índice (número entero positivo, empezando en 0), por lo que mantienen un orden, permitiendo su repetición.
- conjuntos: sus elementos son continuos uno de otro sin importar su orden, impidiendo su repetición.
- mapas: sus elementos son pares llave-valor continuos uno de otro sin importar su orden, impidiendo la repetición de la llave.
Asociación: elementos identificados:
- vectores: sus elementos son identificados por un índice (número entero positivo, empezando en 0).
- conjuntos: sus elementos son identificados por ellos mismos.
- mapas: sus elementos son pares llave-valor, los valores son identificados por la llave.
Además las colecciones no quedan restringidas a contener elementos del mismo tipo por lo que es posible, por ejemplo, tener un solo vector conformado por números, booleanos y caracteres.
Listas: son una secuencia de elementos con la posibilidad de repetirse.
Ejemplos de listas:
'(10 20 30 10 20 30)
'(+ * - /)
(quote (x y z))
(list \a \e \i \á \é \í)
'((1 2 3) (4 5 6) (7 8 9))
La lista
'(\a \e \i)
puede visualizarse de la siguiente manera:
Vectores:
- Son una secuencia de elementos con la posibilidad de repetirse, donde sus elementos están indexados.
Son una asociación de elementos los cuales están identificados por un índice.
Ejemplos de vectores:
[10 20 30 10 20 30]
[true 456 \# nil]
[[10 20] [30 40] [50 60]]
El vector
[\a \e \i]
puede visualizarse de las siguientes maneras:
Conjuntos:
- Son una secuencia de valores irrepetibles.
Son una asociación de pares llave-valor, donde la llave es el mismo valor y no puede repetirse.
Ejemplos de conjuntos:
#{10 20 30 40 50}
#{1987 1999 2001}
#{[10 20] [50 60] [60 50]}
.
El conjunto
#{\a \e \i}
puede visualizarse de las siguientes maneras:
Mapas:
- Son una secuencia de pares llave-valor.
- Son una asociación de llaves y valores.
- Una llave es cualquier otro tipo de expresión literal, no puede repetirse (con respecto a las otras llaves) y le corresponde tener asociado un único valor.
Un valor es cualquier otro tipo de expresión literal el cual puede repetirse.
Ejemplos de mapas:
{\a true, \e true, \i true, \o true, \u true}
{:dia 31 :mes "Febrero" :año 1999}
{:a [9 10 11] :b [19 20 21] :c [29 30 31]}
{[:sabado :domingo] :fin-de-semana}
{:fin-de-semana [:sabado :domingo]}
El mapa
{\a \A \e \E \i \I}
puede visualizarse de las siguientes maneras:
Anidaciones
Es posible anidar las colecciones tanto como uno quiera:
Listas:
- Con listas anidadas:
'((true false false) () (20 25 30) (\x \y \z))
- Con vectores anidados:
'([\a \e \i] [10 20 30] [] [true false true true])
- Con conjuntos anidados:
'(#{10 20 30} #{} #{\A \B \C \D} #{false true})
- Con mapas anidados:
'({0 10 1 20 2 30} {\A \a \B \c \C \c} {})
- Con listas anidadas:
Vectores:
- Con vectores anidados:
[[false false true] [] [30 30 20 20 10 10] [false false true] [\a \A \á \Á]]
- Con listas anidadas:
['(\h \o \l \a) '(90 80 70) '() '(90 80 70)]
- Con conjuntos anidados:
[#{} #{\q \w \e \r \t \y} #{} #{25 100 50 0 75}]
- Con mapas anidados:
[{} {} {1 2 2 3 3 4 4 5} {\A \B \B \C \C \D}]
- Con vectores anidados:
Conjuntos:
- Con conjuntos anidados:
#{#{10 30 50 70} #{} #{\a \e \i \o \u}}
- Con listas anidados:
#{'() '(9 6 3) '(\a \c \e \g)}
- Con vectores anidadas:
#{[5 4 3 2 1] [\h \o \l \a] [true false true] []}
- Con mapas anidados:
#{{2 4 3 6 4 8} {\a \b \b \c \c \d} {} {10 100 100 1000 1000 10000}}
- Con conjuntos anidados:
Mapas:
- Con mapas anidados:
{:a {:x 10 :y 20 :z 30} {:x 10 :y 20 :z 30} :a}
- Con listas anidadas:
{10 '(5 0) '(0 5) 10}
- Con vectores anidados:
{\a [\á \A \Á \ä \Ä \à \À] [\á \A \Á \ä \Ä \à \À] \a}
- Con conjuntos anidados:
{true #{4 8 2 7} #{2 4 8 7} true}
- Con mapas anidados:
Funciones
Función anónima
Su definición se establece mediante la macro fn y al carecer de nombre su uso práctico implica su aplicación inmediata, ser usada como parámetro/devolución de otras funciones o bien ser enlazada (binding) en un contexto.
=> ((fn [x] [(dec x) x (inc x)]) 10)
[9 10 11]
Función de orden superior
Es una función (anónima o no-anónima) la cual puede recibir como parámetro alguna función.
=> (((fn [base]
(fn [altura]
(/ (* base altura) 2))) 50) 3)
75
=> (((fn [x y]
(fn [z]
(+ z (max x y)))) 3 6) 10)
16
Aridad
Es importante identificar la aridad, el número de argumentos esperados, de las funciones.
Aridad constante: siempre esperan el mismo número de argumentos.
Por ejemplo, la función
inc
siempre espera un solo argumento de tipo númerico:=> (inc 10) 11
Sin embargo, al serle dado un número distinto de argumentos:
=> (inc) ... Wrong number of args (0) passed to: core/inc ... => (inc 10 20) ... Wrong number of args (2) passed to: core/inc ...
Aridad múltiple: esperan cierto número de argumentos quedando algunos de ellos como opcionales.
Por ejemplo, la función
range
puede esperar de 0 a 3 argumentos de tipo númerico:=> (range) (0 1 2 3 4 ... ∞) => (range 10) (0 1 2 3 4 5 6 7 8 9) => (range 5 10) (5 6 7 8 9) => (range 1 10 2) (1 3 5 7 9)
Sin embargo, al serle dado un número distinto de argumentos:
=> (range 1 10 2 3) ... Wrong number of args (4) passed to: core/range ... => (range 1 10 2 3 4) ... Wrong number of args (5) passed to: core/range ...
Aridad variable: esperan de 0 a n número de argumentos:
Por ejemplo, la función
+
puede esperar de 0 a n argumentos de tipo númerico:=> (+) 0 => (+ 10) 10 => (+ 10 20) 30 => (+ 10 20 30) 60 => (+ 10 20 30 40) 100
Bindings
Un binding (enlace o vínculo) es la asociación de un nombre con un valor dentro de cierto contexto.
defn
Establece el enlace entre un símbolo y un valor en un contexto global: el símbolo será el nómbre de una función y el valor será la definición de dicha función.
=> (defn fin-de-semana?
[dias]
(and (= (count dias) 2)
(or (and (= (first dias) :sabado)
(= (last dias) :domingo))
(and (= (first dias) :domingo)
(= (last dias) :sabado)))))
=> (fin-de-semana? [:viernes :sabado])
false
=> (fin-de-semana? [:domingo :lunes])
false
=> (fin-de-semana? [])
false
=> (fin-de-semana? [:sabado :domingo])
true
=> (fin-de-semana? [:domingo :sabado])
true
=> (defn el-antecesor-de
[número]
(dec número))
=> (defn el-sucesor-de
[número]
(inc número))
=> (defn el-antecesor-y-el-sucesor-de
[número]
(hash-map
:el-antecesor (el-antecesor-de número)
:el-sucesor (el-sucesor-de número)))
=> (el-antecesor-y-el-sucesor-de 10)
{:el-sucesor 11, :el-antecesor 9}
=> (el-antecesor-y-el-sucesor-de -20)
{:el-sucesor -19, :el-antecesor -21}
let
Establece el enlace entre un símbolo y un valor en un contexto local: el símbolo será
el nombre dado a un valor (forma normal) y el valor podrá ser llamado en el cuerpo
de let
.
=> (defn fin-de-semana?
[dias]
(let [son-dos? (fn [dias]
(= (count dias) 2))
son-sabado-domingo? (fn [dias]
(and (= (first dias) :sabado)
(= (last dias) :domingo)))
son-domingo-sabado? (fn [dias]
(and (= (first dias) :domingo)
(= (last dias) :sabado)))]
(and (son-dos? dias)
(or (son-sabado-domingo? dias)
(son-domingo-sabado? dias)))))
=> (fin-de-semana? [:domingo :lunes :martes])
false
=> (fin-de-semana? [])
false
=> (fin-de-semana? [:domingo :sabado])
true
=> (fin-de-semana? [:sabado :domingo])
true
=> (defn el-antecesor-y-el-sucesor-de
[número]
(let [el-antecesor-de (fn [número] (dec número))
el-sucesor-de (fn [número] (inc número))
un-mapa-con (fn [x y]
(hash-map :el-antecesor x
:el-sucesor y))]
(un-mapa-con (el-antecesor-de número)
(el-sucesor-de número))))
=> (el-antecesor-y-el-sucesor-de 10)
{:el-sucesor 11, :el-antecesor 9}
=> (el-antecesor-y-el-sucesor-de -10)
{:el-sucesor -9, :el-antecesor -11}
Condicionales
if
Macro que dada 3 formas las evalua de la siguiente manera:
- Si la primera es resuelta como verdadero solo se evalua la segunda.
- Si la primera es resuelta como falso solo se evalua la tercera.
Ejemplo:
=> (defn par-ordenado
[xs]
(if (not= (count xs) 2)
xs
(if (< (first xs) (second xs))
xs
(vector (second xs) (first xs)))))
=> (par-ordenado [10 20])
[10 20]
=> (par-ordenado [20 10])
[10 20]
cond
Macro que dado pares de formas las evalua de la siguiente manera:
- Si la primera es resuelta como verdadero solo evalua la segunda.
- Si la tercera es resuelta como verdadero solo evalua la cuarta.
- Si la quinta es resuelta como verdadero solo evalua la sexta.
- etc.
Ejemplo:
=> (defn la-estación-del-año-de
[mes]
(let [estaciones (hash-map :primavera #{:marzo :abril :mayo}
:verano #{:junio :julio :agosto}
:otoño #{:septiembre :octubre :noviembre}
:invierno #{:diciembre :enero :febrero})]
(cond ((estaciones :primavera) mes) :primavera
((estaciones :verano) mes) :verano
((estaciones :otoño) mes) :otoño
((estaciones :invierno) mes) :invierno)))
=> (la-estación-del-año-de :noviembre)
:otoño
=> (la-estación-del-año-de :diciembre)
:invierno
case
Macro que dado una primera forma y pares de formas las evalua de la siguiente manera:
- Si la primera es igual a la segunda evalua la tercera.
- Si la primera es igual a la cuarta evalua la quinta.
- Si la primera es igual a la sexta evalua la septima.
- etc.
Ejemplo:
(defn el-siguiente-día
[a]
(case a
:lunes :martes
:martes :miércoles
:miércoles :jueves
:jueves :viernes
:viernes :sabado
:sabado :domingo
:domingo :lunes))
=> (el-siguiente-día :miércoles)
:jueves
=> (el-siguiente-día :domingo)
:lunes
Funciones de orden superior
Clojure cuenta con algunas funciones de orden superior ya conocidas (o clásicas) dentro de cualquier lenguaje funcional. Estas funciones son conocidas ya que surgen de soluciones a problemas recurrentes en la programación.
El documento Why Functional Programming Matters (1984) de John Hughes identifica dos carácteristicas importantes de la programación funcional: funciones de orden superior y evaluación perezosa.
apply
Dada una función y una secuencia, aplica la función con los elementos de la secuencia como sus argúmentos:
=> (apply + [10 20 30 40 50])
150
=> (apply + [10 -20 30])
20
=> (apply str ["¡Hola" " " "mundo" "!"])
¡Hola mundo!
=> (apply max [10 -20 30 -40 50 -60])
50
=> (defn los-primeros-en
[xs ys]
(vector (first xs) (first ys)))
=> (apply los-primeros-en [[30 20] [50 60]])
[30 50]
Se puede considerar a apply como una rescritura de expresión:
(apply + [3 4 5])
es rescrita como(+ 3 4 5)
.(apply max [3 4 5])
es rescrita como(max [3 4 5])
.(apply str ["correo" "@" "servidor" "." "com"])
es rescrita como(str "correo" "@" "servidor" "." "com")
.
map
Dada una función y una secuencia, aplica la función con cada uno de los elementos de la secuencia regresando una secuencia con los resultados obtenidos:
=> (map (fn [número] (* 2 número)) [])
()
=> (map (fn [número] (* 2 número)) [2 4 6 8 9])
(4 8 12 16 18)
=> (map (fn [nombre] (str "¡Hola " nombre "!")) ["A" "B" "C" "D"])
("¡Hola A!" "¡Hola B!" "¡Hola C!" "¡Hola D!")
=> (map (fn [booleano] (if (true? booleano) 1 0)) [true false false true false true])
(1 0 0 1 0 1)
=> (map count [[10 20] [30 40 50] [] [60 70 80 90] [100]])
(2 3 0 4 1)
=> (map first [[10 20] [30 40 50] [] [60 70 80 90] [100]])
(10 30 nil 60 100)
filter
Dada una función (que recibe un parámetro y regresa verdadero o falso) y una secuencia, aplica la función con cada uno de los elementos de la secuencia regresando una secuencia con los elementos para los cuales la función regresó verdadero:
=> (filter pos? [-3 2 -1 0 1 -2 3])
(2 1 3)
=> (filter neg? [-3 2 -1 0 1 -2 3])
(-3 -1 -2)
=> (defn es-mayor-a-diez-y-menor-a-veinte?
[x]
(and (> x 10) (< x 20)))
=> (filter es-mayor-a-diez-y-menor-a-veinte? [34 12 10 -45 26 9 79 17 20 11])
(12 17 11)
=> (defn contiene-solo-un-elemento?
[xs]
(= (count xs) 1))
=> (filter contiene-solo-un-elemento? [[:a :b] [:c] [] [:d :e :f] [:g]])
([:c] [:g])
La función dada a filter se llama predicado y se dice que los elementos filtrados de la secuencia cumplen el predicado.
reduce
Dada una función, opcionalmente un valor inicial, y una secuencia, aplica la función con el valor inicial obteniendo un nuevo valor. La función es aplicada una vez más, ahora con el nuevo valor y el primer elemento de la secuencia obteniendo un nuevo valor. La función es aplicada una vez más, ahora con el nuevo valor y el segundo elemento de la secuencia obteniendo un nuevo valor y así sucesivamente hasta usar todos los elementos de la secuencia.
Otro nombre para reduce es fold.
=> (reduce + 0 [1 2 3 4 5])
15
Ya que +
es una función de aridad múltiple no requiere del valor inicial, el 0
:
=> (reduce + [1 2 3 4 5])
15
Aunque esto permite iniciar la suma desde cualquier otra cantidad:
=> (reduce + -40 [1 2 3 4 5])
-25
=> (reduce + 100 [1 2 3 4 5])
115
Haciendo uso de una función que sea de aridad múltiple:
=> (reduce (fn
([] 0)
([x y] (+ x y)))
[])
0
=> (reduce (fn
([] 0)
([x y] (+ x y)))
[10])
10
=> (reduce (fn
([] 0)
([x y] (+ x y)))
[10 20])
30
=> (reduce (fn
([] 0)
([x y] (+ x y)))
[10 20 30])
60
Debe observarse que la función de aridad múltiple ha de esperar cero argumentos o dos argumentos.
=> (reduce (fn
([] 0)
([x xs] (+ x (count xs))))
[])
0
=> (reduce (fn
([] 0)
([x xs] (+ x (count xs))))
0 [ [4] ])
1
=> (reduce (fn
([] 0)
([x xs] (+ x (count xs))))
0 [ [17] [8 12] ])
3
=> (reduce (fn
([] 0)
([x xs] (+ x (count xs))))
0 [ [42] [120 73] [-32 -9 81] ])
6
Sobre el valor inicial
El valor inicial dado a reduce pueder ser visto como el estado inicial para la reducción o como un acumulador para la reducción en un uso práctico.
=> (conj #{} 10 20 30 20 40 30 50 10)
#{20 50 40 30 10}
La función conj añade a una colección, en este caso un conjunto, los elementos dados como subsiguientes parámetros, por lo tanto:
=> (reduce (fn
([] #{})
([conjunto elemento] (conj conjunto elemento)))
#{} [ [10 20] [30] [40 50] [10 20] [30] ])
#{[40 50] [10 20] [30]}
De un conjunto vacío usado como acumulador, se obtienen las secuencias (vectores) que no existen previamente en el acumulador.
Otros ejemplos donde se hace uso del valor inicial como acumulador:
=> (reduce (fn
([mapa número]
(conj mapa [número (inc número)] )))
{} [30 -4 50 -6 70])
{30 31, -4 -3, 50 51, -6 -5, 70 71}
=> (reduce (fn
([mapa número]
(conj mapa [número (inc número)] )))
{} [30 30 30 30])
{30 31}
Namespace
La organización de un proyecto en Clojure se realiza mediante espacios de nombre (namespace), los cuales
mantienen agrupados una colección de bindings y suelen corresponder a un archivo dentro del proyecto con la
extensión .clj
.
Una vez iniciado el REPL uno se encuentra en un namespace. Suponiendo que uno tenga el archivo
proyecto/src/proyecto/core.clj
y el contenido de dicho archivo sea:
(ns proyecto.core)
(defn factorial [x]
(reduce * 1 (range 1 (inc x))))
(defn factoriales [xs]
(map factorial xs))
Entonces, al iniciar el REPL mediante dicho archivo:
Para obtener el namespace actual donde uno se encuentra:
=> *ns* #object[clojure.lang.Namespace ... "proyecto.core"]
Para hacer uso de los bindings agrupados en el namespace actual:
=> (factorial 3) 6 => (factorial 5) 120 => (factoriales []) () => (factoriales [0 1 2 3 4 5]) (1 1 2 6 24 120)
Indicando el namespace del binding:
=> (proyecto.core/factorial 4) 24 => (proyecto.core/factoriales 0 5) (1 1 2 6 24 120)
Cabe notar que la primera línea ((ns proyecto.core)
) encontrada en el archivo proyecto/src/proyecto/core.clj
establece el namespace en el que uno se encontrará al iniciar el REPL.
Para hacer uso de bindings encontrados en otro namespace:
Indicando el namespace del binding:
Por ejemplo el namespace clojure.string agrupa los bindings
blank?
,lower-case
,upper-case
(entre otros) que operan sobre una cadena de caracteres:=> (clojure.string/blank? "") true => (clojure.string/blank? " ") true => (clojure.string/blank? "¡Hola mundo!") false => (clojure.string/lower-case "¡Hola mundo!") "¡hola mundo!" => (clojure.string/upper-case "¡Hola mundo!") "¡HOLA MUNDO!"
Haciendo uso del keyword
:require
conns
:(ns proyecto.core (:require [clojure.string :as str]) (:require [proyecto.otronamespace :as otrons])) (defn f [x] (map str/lower-case xs)) (defn g [xs] (filter (fn [x] (not (str/blank? x))) xs))
En este caso se ha requerido el acceso a los bindings encontrados en los namespace
clojure.string
yproyecto.otronamespace
. A su vez se ha dado un alias a los anteriores namespace mencionados:clojure.string
será conocido comostr
yproyecto.otronamespace
comootrons
, de tal manera que las funcionesf
yg
hagan uso de ellos.
Resumen de funciones
Se enlistan y ejemplifican algunas funciones en Clojure.
Recursividad
Se entiende a la recursividad como la capacidad de una función de aplicarse en su propio cuerpo, lo que conlleva a plantear la situación bajo la cual ha de detenerse ya que, de no ser así, existe la posibilidad de que la aplicación generé un ciclo infinito. A la situación que determina el fin de aplicaciones sucesivas se conoce como caso base.
=> (defn el-producto-de
[x y]
(if (zero? y)
0
(+ x (el-producto-de x (dec y)))))
=> (el-producto-de 3 4)
12
letfn
Macro que permite definir una función no-anónima en el contexto local de otra función, con la posibilidad de que la función no-anónima se aplique a si misma u a otra función no-anónima en el contexto local.
=> (defn el-producto-de
[x y]
(letfn [(sumatoria
[inicio fin sumando total]
(if (= inicio fin)
total
(sumatoria (inc inicio)
fin
sumando
(+ sumando total))))]
(sumatoria 0 x y 0)))
=> (el-producto-de 3 2)
6
=> (el-producto-de 0 5)
0
=> (el-producto-de 6 1)
6
=> (defn la-suma-de-números-en
[un-vector]
(letfn [(sumatoria
[inicio fin secuencia total]
(if (= inicio fin)
total
(sumatoria (inc inicio)
fin
(rest secuencia)
(+ (first secuencia) total))))]
(sumatoria 0 (count un-vector) un-vector 0)))
=> (la-suma-de-números-en [])
0
=> (la-suma-de-números-en [10])
10
=> (la-suma-de-números-en [10 20])
30
=> (la-suma-de-números-en [10 20 30 40 50])
150
=> (la-suma-de-números-en [10 -20 30])
20
Recursividad sobre colecciones
La recursividad permitirá operar sobre los elementos de una colección dada accediendo a ellos uno por uno.
Listas
first y rest: acceder del primer elemento a los siguientes.
=> (let [una-lista '(10 20 30)] (list (first una-lista) (rest una-lista))) (10 (20 30)) => (let [una-lista '(20 30)] (list (first una-lista) (rest una-lista))) (20 (30)) => (let [una-lista '(30)] (list (first una-lista) (rest una-lista))) (30 ()) => (let [una-lista '()] (list (first una-lista) (rest una-lista))) (nil ())
last y butlast: acceder del último elemento a los primeros.
=> (let [una-lista '(10 20 30)] (list (last una-lista) (butlast una-lista))) (30 (10 20)) => (let [una-lista '(10 20)] (list (last una-lista) (butlast una-lista))) (20 (10)) => (let [una-lista '(10)] (list (last una-lista) (butlast una-lista))) (10 nil) => (let [una-lista '()] (list (last una-lista) (butlast una-lista))) (nil nil)
cons:
De algo menor a algo mayor:
=> (let [una-lista '()] (cons 10 una-lista)) (10) => (let [una-lista '(10)] (cons 20 una-lista)) (20 10) => (let [una-lista '(20 10)] (cons 30 una-lista)) (30 20 10)
De algo mayor a algo menor:
=> (let [una-lista '()] (cons 30 una-lista)) (30) => (let [una-lista '(30)] (cons 20 una-lista)) (20 30) => (let [una-lista '(20 30)] (cons 10 una-lista)) (10 20 30)
Suma de números en una lista:
(let [suma (fn [xs] (letfn [(r [as acc] (if (empty? as) acc (r (rest as) (+ acc (first as)))))] (r xs 0)))] (suma '(10 20 30))) 60
Cadenas de caracteres
first y rest:
=> (let [una-cadena-de-caracteres "123"] (list (first una-cadena-de-caracteres) (rest una-cadena-de-caracteres))) (\1 (\2 \3)) => (let [una-cadena-de-caracteres "23"] (list (first una-cadena-de-caracteres) (rest una-cadena-de-caracteres))) (\2 (\3)) => (let [una-cadena-de-caracteres "3"] (list (first una-cadena-de-caracteres) (rest una-cadena-de-caracteres))) (\3 ()) => (let [una-cadena-de-caracteres ""] (list (first una-cadena-de-caracteres) (rest una-cadena-de-caracteres))) (nil ())
last y butlast:
=> (let [una-cadena-de-caracteres "123"] (list (last una-cadena-de-caracteres) (butlast una-cadena-de-caracteres))) (\3 (\1 \2)) => (let [una-cadena-de-caracteres "12"] (list (last una-cadena-de-caracteres) (butlast una-cadena-de-caracteres))) (\2 (\1)) => (let [una-cadena-de-caracteres "1"] (list (last una-cadena-de-caracteres) (butlast una-cadena-de-caracteres))) (\1 nil) => (let [una-cadena-de-caracteres ""] (list (last una-cadena-de-caracteres) (butlast una-cadena-de-caracteres))) (nil nil)
srt:
Del inicio al fin:
=> (let [una-cadena-de-caracteres ""] (str una-cadena-de-caracteres \1)) 1 => (let [una-cadena-de-caracteres "1"] (str una-cadena-de-caracteres \2)) 12 => (let [una-cadena-de-caracteres "12"] (str una-cadena-de-caracteres \3)) 123
Del fin al inicio:
=> (let [una-cadena-de-caracteres ""] (str \1 una-cadena-de-caracteres)) 1 => (let [una-cadena-de-caracteres "1"] (str \2 una-cadena-de-caracteres)) 21 => (let [una-cadena-de-caracteres "21"] (str \3 una-cadena-de-caracteres)) 321
Número de veces repetido el carácter \O en una cadena de caracteres:
=> (let [repeticiones (fn [xs] (letfn [(r [as acc] (if (empty? as) acc (r (rest as) (if (= (first as) \O) (inc acc) acc))))] (r xs 0)))] (repeticiones "¡HOLA MUNDO!")) 2
Conjuntos
first y rest:
=> (let [un-conjunto #{10 20 30}] (list (first un-conjunto) (rest un-conjunto))) (20 (30 10)) => (let [un-conjunto #{10 30}] (list (first un-conjunto) (rest un-conjunto))) (30 (10)) => (let [un-conjunto #{10}] (list (first un-conjunto) (rest un-conjunto))) (10 ()) => (let [un-conjunto #{}] (list (first un-conjunto) (rest un-conjunto))) (nil ())
last y butlast:
=> (let [un-conjunto #{10 20 30}] (list (last un-conjunto) (butlast un-conjunto))) (10 (20 30)) => (let [un-conjunto #{20 30}] (list (last un-conjunto) (butlast un-conjunto))) (30 (20)) => (let [un-conjunto #{20}] (list (last un-conjunto) (butlast un-conjunto))) (20 nil) => (let [un-conjunto #{}] (list (last un-conjunto) (butlast un-conjunto))) (nil nil)
conj:
=> (let [un-conjunto #{}] (conj un-conjunto 10)) #{10} => (let [un-conjunto #{10}] (conj un-conjunto 20)) #{20 10} => (let [un-conjunto #{10 20}] (conj un-conjunto 30)) #{20 30 10}
Suma de números en un conjunto:
=> (let [suma (fn [xs] (letfn [(r [as acc] (if (empty? as) acc (r (rest as) (+ acc (first as)))))] (r xs 0)))] (suma #{10 20 30})) 60
Vectores
first y rest:
=> (let [un-vector [10 20 30]] (list (first un-vector) (rest un-vector))) (10 (20 30)) => (let [un-vector [20 30]] (list (first un-vector) (rest un-vector))) (20 (30)) => (let [un-vector [30]] (list (first un-vector) (rest un-vector))) (30 ()) => (let [un-vector []] (list (first un-vector) (rest un-vector))) (nil ())
last y butlast:
=> (let [un-vector [10 20 30]] (list (last un-vector) (butlast un-vector))) (30 (10 20)) => (let [un-vector [10 20]] (list (last un-vector) (butlast un-vector))) (20 (10)) => (let [un-vector [10]] (list (last un-vector) (butlast un-vector))) (10 nil) => (let [un-vector []] (list (last un-vector) (butlast un-vector))) (nil nil)
conj:
=> (let [un-vector []] (conj un-vector 10)) [10] => (let [un-vector [10]] (conj un-vector 20)) [10 20] => (let [un-vector [10 20]] (conj un-vector 30)) [10 20 30]
cons:
=> (let [un-vector []] (cons 10 un-vector)) (10) => (let [un-vector [10]] (cons 20 un-vector)) (20 10) => (let [un-vector [20 10]] (cons 30 un-vector)) (30 20 10)
Suma de números en un vector:
=> (let [suma (fn [xs] (letfn [(r [as acc] (if (empty? as) acc (r (rest as) (+ acc (first as)))))] (r xs 0)))] (suma [10 20 30])) 60
Mapas
first y rest:
=> (let [un-mapa {:a 10 :b 20 :c 30}] (list (first un-mapa) (rest un-mapa))) ([:a 10] ([:b 20] [:c 30])) => (let [un-mapa {:b 20 :c 30}] (list (first un-mapa) (rest un-mapa))) ([:b 20] ([:c 30])) => (let [un-mapa {:c 30}] (list (first un-mapa) (rest un-mapa))) ([:c 30] ()) => (let [un-mapa {}] (list (first un-mapa) (rest un-mapa))) (nil ())
last y butlast:
=> (let [un-mapa {:a 10 :b 20 :c 30}] (list (last un-mapa) (rest un-mapa))) ([:c 30] ([:b 20] [:c 30])) => (let [un-mapa {:a 10 :b 20}] (list (last un-mapa) (rest un-mapa))) ([:b 20] ([:b 20])) => (let [un-mapa {:a 10}] (list (last un-mapa) (rest un-mapa))) ([:a 10] ()) => (let [un-mapa {}] (list (last un-mapa) (rest un-mapa))) (nil ())
conj:
=> (let [un-mapa {}] (conj un-mapa [:a 10])) {:a 10} => (let [un-mapa {:a 10}] (conj un-mapa [:b 20])) {:a 10, :b 20} => (let [un-mapa {:a 10 :b 20}] (conj un-mapa [:c 30])) {:a 10, :b 20, :c 30}
Suma de números en un conjunto:
=> (let [suma (fn [xs] (letfn [(r [as acc] (if (empty? as) acc (r (rest as) (+ acc ((first as) 1)))))] (r xs 0)))] (suma {:a 10 :b 20 :c 30})) 60
Implementación de funciones de orden superior sobre las colecciones mediante recursividad
;;;; Funciones de Orden Superior / Higher Order Functions:
;;;; map, filter y reduce
;;;; sobre colecciones:
;;;; listas, cadenas de caracteres, conjuntos, vectores y mapas
;;;;
;; Listas
;;;;
(defn lmap
"Dada una función f y una lista xs regresa una lista con
los resultados de aplicar a f sobre los elementos de xs."
[f xs]
(letfn [(r [as bs g]
(if (empty? as)
bs
(r (butlast as)
(cons (g (last as)) bs)
g)))]
(r xs '() f)))
(defn lfilter
"Dado un predicado f y una lista xs regresa una lista con
los elementos de xs que cumplan el predicado."
[f xs]
(letfn [(r [as bs g]
(if (empty? as)
bs
(r (butlast as)
(if (true? (g (last as)))
(cons (last as) bs)
bs)
g)))]
(r xs '() f)))
(defn lreduce
"Dada una función f, un valor inicial vi y una lista xs
regresa un valor final vf como el resultado de aplicar a
f sobre vi y cada uno de los elementos de xs."
[f vi xs]
(letfn [(r [as vf g]
(if (empty? as)
vf
(r (butlast as)
(g vf (last as))
g)))]
(r xs vi f)))
;;;;
;; Cadenas de caracteres
;;;;
(defn smap
"Dada una función f y una cadena de caracteres xs regresa
una cadena de caracteres con los resultados de aplicar a
f sobre los caracteres de xs."
[f xs]
(letfn [(r [as bs g]
(if (empty? as)
bs
(r (rest as)
(str bs (g (first as)))
g)))]
(r xs "" f)))
(defn sfilter
"Dado un predicado f y una cadena de caracteres xs regresa
una cadena de caracteres con los caracteres de xs que cumplan
el predicado."
[f xs]
(letfn [(r [as bs g]
(if (empty? as)
bs
(r (rest as)
(if (true? (g (first as)))
(str bs (first as))
bs)
g)))]
(r xs "" f)))
(defn sreduce
"Dada una función f, un valor inicial vi y una cadena de
caracteres xs regresa un valor final vf como el resultado
de aplicar a f sobre vi y cada uno de los caracteres de xs."
[f vi xs]
(letfn [(r [as vf g]
(if (empty? as)
vf
(r (rest as)
(g vf (first as))
g)))]
(r xs vi f)))
;;;;
;; Conjuntos
;;;;
(defn cmap
"Dada una función f y un conjunto xs regresa un conjunto con
los resultados de aplicar a f sobre los elementos de xs."
[f xs]
(letfn [(r [as bs g]
(if (empty? as)
bs
(r (rest as)
(conj bs (g (first as)))
g)))]
(r xs #{} f)))
(defn cfilter
"Dado un predicado f y un conjunto xs regresa un conjunto con
los elementos de xs que cumplan el predicado."
[f xs]
(letfn [(r [as bs g]
(if (empty? as)
bs
(r (rest as)
(if (true? (g (first as)))
(conj bs (first as))
bs)
g)))]
(r xs #{} f)))
(defn creduce
"Dada una función f, un valor inicial vi y una lista xs
regresa un valor final vf como el resultado de aplicar a
f sobre vi y cada uno de los elementos de xs."
[f vi xs]
(letfn [(r [as vf g]
(if (empty? as)
vf
(r (rest as)
(g vf (first as))
g)))]
(r xs vi f)))
;;;;
;; Vectores
;;;;
(defn vmap
[f xs]
"Dada una función f y un vector xs regresa un vector con
los resultados de aplicar a f sobre los elementos de xs."
(letfn [(r [as bs g]
(if (empty? as)
bs
(r (rest as)
(conj bs (g (first as)))
g)))]
(r xs [] f)))
(defn vfilter
"Dado un predicado f y un vector xs regresa un vector con
los elementos de xs que cumplan el predicado."
[f xs]
(letfn [(r [as bs g]
(if (empty? as)
bs
(r (rest as)
(if (true? (g (first as)))
(conj bs (first as))
bs)
g)))]
(r xs [] f)))
(defn vreduce
"Dada una función f, un valor inicial vi y un vector xs
regresa un valor final vf como el resultado de aplicar a
f sobre vi y cada uno de los elementos de xs."
[f vi xs]
(letfn [(r [as vf g]
(if (empty? as)
vf
(r (rest as)
(g vf (first as))
g)))]
(r xs vi f)))
;;;;
;; Mapas
;;;;
(defn mmap
"Dada una función f y un mapa xs regresa un mapa con
las llaves de xs asociadas al resultado de aplicar
a f sobre el valor asociado a dichas llaves."
[f xs]
(letfn [(r [as bs g]
(if (empty? as)
bs
(r (rest as)
(conj bs [((first as) 0) (g ((first as) 1))])
g)))]
(r xs {} f)))
(defn mfilter
"Dado un predicado f y un mapa xs regresa un mapa con
los elementos de xs que cumplan el predicado en su valor."
[f xs]
(letfn [(r [as bs g]
(if (empty? as)
bs
(r (rest as)
(if (true? (g ((first as) 1)))
(conj bs [((first as) 0) ((first as) 1)])
bs)
g)))]
(r xs {} f)))
(defn mreduce
"Dada una función f, un valor inicial vi y un mapa xs
regresa un valor final vf como el resultado de aplicar a
f sobre vi y cada uno de los elementos de xs."
[f vi xs]
(letfn [(r [as vf g]
(if (empty? as)
vf
(r (rest as)
(g vf (first as))
g)))]
(r xs vi f)))
Composición de funciones
Se entiende a la composición de funciones como una operación que a partir de la definición de dos funciones permite obtener una nueva función, a la función así definida se conoce como función compuesta.
=> (defn el-triple-de
[x]
(* 3 x))
=> (defn la-mitad-de
[x]
(/ x 2))
=> (defn la-composición-de
[g f]
(fn [x] (g (f x))))
Mediante la función la-composición-de
se puede definir nuevas funciones a partir de funciones
ya existentes:
=> (defn el-triple-de-la-mitad-de
[x]
((la-composición-de la-mitad-de el-triple-de) x))
=> (defn la-mitad-del-triple-de
[x]
((la-composición-de el-triple-de la-mitad-de) x))
De esta forma:
=> (la-mitad-del-triple-de 8)
12
=> (el-triple-de-la-mitad-de 8)
12
=> (la-mitad-del-triple-de 12)
18
=> (el-triple-de-la-mitad-de 16)
24
Clojure provee de una función de orden superior para llevar a cabo la composición de funciones, comp:
=> ((comp el-triple-de la-mitad-de) 12)
18
=> ((comp la-mitad-de el-triple-de) 16)
24
=> ((comp el-triple-de el-triple-de el-triple-de) 3)
81
Se establece que:
La composición de funciones en general no es conmutativa - Wikipedia
Por lo que a partir de las siguiente funciones:
=> (defn solo-los-positivos
[xs]
(filter pos? xs))
=> (defn incrementados-en-cinco
[xs]
(map (fn [x] (+ x 5)) xs))
Se puede observar que el resultado difiere debido al orden en que se establece la composición:
=> ((comp solo-los-positivos incrementados-en-cinco) [-7 -3 2 -1 8])
(2 7 4 13)
=> ((comp incrementados-en-cinco solo-los-positivos) [-7 -3 2 -1 8])
(7 13)
=> ((comp solo-los-positivos incrementados-en-cinco) [-3])
(2)
=> ((comp incrementados-en-cinco solo-los-positivos) [-3])
()