Clojure reconoce dos tipos de estructuras:
Representaciones literales de las estructuras de datos (como números, cadenas, mapas, y vectores)
Operaciones
Utilizamos el término Form para referirnos a un código o expresión válida.
Las siguientes representaciones literales son todas Formas válidas:
1
"a string"
["a" "vector" "of" "strings"]
1 "a string" ["a" "vector" "of" "strings"]
Los literales se usan en las operaciones. Las operaciones son las queda dicen la forma de hacer las cosas. Todas las operaciones tienen la estructura paréntesis, operador, operandos, paréntesis: (operator operand1 operand2 ... operandn) Como por ejemplo:
(+ 1 2 3)
(str "It was the panda " "in the library " "with a dust buster")
6 "It was the panda in the library with a dust buster"
(def a 10)
a
#'user/a 10
(if true
"By Zeus's hammer!"
"By Aquaman's trident!")
(if false
"By Zeus's hammer!"
"By Aquaman's trident!")
(if false
"By Odin's Elbow!")
"By Zeus's hammer!" "By Aquaman's trident!" nil
El operador do permite agrupar múltiples formas, y ejecutar cada una de ellas:
(if true
(do
(def a 1)
(def b 2)
(+ a b)
)
(do (println "Failure!")
"By Aquaman's trident!"))
3
El operador When es como una combinación de if y do, pero sin la rama else. Ejemplo:
(when true
(def a "Success! ")
(str a "abra cadabra") )
"Success! abra cadabra"
Clojure tiene valores true and false. nil se utiliza para indicar ningún valor. Se puede comprobar si un valor es nil la funcion nil?
(nil? 1)
(nil? nil)
false true
Tanto nil y false se utilizan para representar falsedad, mientras que todos los demás valores son lógicamente verdaderos.
(if "bears eat beets"
"bears beets Battlestar Galactica")
"bears beets Battlestar Galactica"
(if nil
"This won't be the result because nil is falsey"
"nil is falsey")
"nil is falsey"
EL operador de igualdad es =
(= 1 1)
(= nil nil)
(= "hola" "Hola")
(= 1 "1")
true true false false
or retorna el primer valor verdadero o si no, el último valor.
(or false nil :large_I_mean_venti :why_cant_I_just_say_large)
:large_I_mean_venti
(or (= 0 1) (= "yes" "no"))
false
and retorna el primer valor verdadero o si no, el ultimo valor verdadero.
(and :free_wifi :hot_coffee)
:hot_coffee
(and :feelin_super_cool nil false)
nil
Todas las estructuras de datos de Clojure son inmutables.
Integer, float, ratio, strings.
93
1.2
1/5
(println "\"Great cow of Moscow!\" - Hermes Conrad")
93 1.2 1/5 nil
Clojure sólo permite comillas dobles para delimitar cadenas. 'Lord Voldemort', por ejemplo, no es una cadena válida. Clojure no tiene interpolación de cadenas. Sólo se permite la concatenación a través de la función str:
(def name "Chewbacca")
(str "\"Uggllglglglglglglglll\" - " name)
#'user/name "\"Uggllglglglglglglglll\" - Chewbacca"
En este ejemplo :first-name and :last-name are keywords, un diccionario se declara con {}
{:first-name "Charlie"
:last-name "McFishwich"}
{:first-name "Charlie", :last-name "McFishwich"}
Aquí asociamos la llave "string-key" con la funcion +
{"string-key" +}
{"string-key" #object[clojure.core$_PLUS_ 0x2a00e604 "clojure.core$_PLUS_@2a00e604"]}
Los mapas pueden ser anidados
{:name {:first "John" :middle "Jacob" :last "Jingleheimerschmidt"}}
{:name {:first "John", :middle "Jacob", :last "Jingleheimerschmidt"}}
También se puede usar la función hash-map
(hash-map :a 1 :b 2)
{:b 2, :a 1}
Para acceder a un valor se usa la función get
(get {:a 0 :b 1} :b)
1
(get {:a 0 :b {:c "ho hum"}} :b)
{:c "ho hum"}
si no se encuentra el valor, se retorna nil o se puede dar un valor por defecto como "unicorns?"
(get {:a 0 :b 1} :c)
(get {:a 0 :b 1} :c "unicorns?")
nil "unicorns?"
La funcion get-in permite buscar en mapas anidados
(get-in {:a 0 :b {:c "ho hum"}} [:b :c])
"ho hum"
Otra manera de buscar un valor, es usar el mapa como una funcion con la llave como argumento
({:name "The Human Coffeepot"} :name)
"The Human Coffeepot"
Pueden ser usadas como funciones que buscan en un mapa su correspondiente valor. ejemplos de keywords: :a :rumplestiltsken :34 :_?
(:a {:a 1 :b 2 :c 3})
1
Obtener el elemento en el indice 0
(get [3 2 1] 0)
3
Obtener el elemento en el indice 1
(get ["a" {:name "Pugsley Winterbottom"} "c"] 1)
{:name "Pugsley Winterbottom"}
Se pueden crear vectores con la función vector
(vector "creepy" "full" "moon")
["creepy" "full" "moon"]
Se pueden añadir elementos al final de un vector con la funcion conj
(conj [1 2 3] 4)
[1 2 3 4]
Las listas son coleciones lineales de valores al igual que los vectores, pero con algunas diferencias, como por ejemplo, no se puede usar la funcion get.
'(1 2 3 4)
(1 2 3 4)
Para obtener un valor, se usa la funcion nth
(nth '(:a :b :c) 0)
(nth '(:a :b :c) 2)
:a :c
Las listas, al igual que los vectores pueden contener cualquier tipo de valor, tambien se pueden declarar con la funcion list.
(list 1 "two" {3 4})
(1 "two" {3 4})
Se pueden insertar elementos al comienzo de la lista con conj
(conj '(1 2 3) 4)
(4 1 2 3)
Los sets con coleciones de valores únicos, una notacion literal para un set puede ser:
#{"kurt vonnegut" 20 :icicle}
#{20 :icicle "kurt vonnegut"}
Tambien se puede usar la función hash-set
(hash-set 1 1 2 2)
#{1 2}
Si se agraga un valor a un set que ya se encuentra, este se rechaza:
(conj #{:a :b :d} :b)
#{:b :d :a}
(conj #{:a :b :d} :c)
#{:c :b :d :a}
Se pueden crear sets a partir de listas o vectores ya creados
(set [3 3 3 4 4])
#{4 3}
Se puede mirar si un set contienen un elemento con la funcion get, un keyword o la funcion contains?
(contains? #{:a :b} :a)
(contains? #{:a :b} 3)
true false
(:a #{:a :b})
(:c #{:a :b})
:a nil
(get #{:a :b} :a)
(get #{:a :b} "kurt vonnegut")
:a nil
Esta es una expresion que retorna la funcion +
(or + -)
#object[clojure.core$_PLUS_ 0x2a00e604 "clojure.core$_PLUS_@2a00e604"]
Ejemplos de expresiones que retornan 6:
((or + -) 1 2 3)
6
((and (= 1 1) +) 1 2 3)
6
((first [+ 0]) 1 2 3)
6
Las funciones pueden tomar como parametros otras funciones o retornar funciones, estas funciones se llaman "higher-order functions" lenguajes que permiten hacer esto, se dice que soportan "first-class functions".
Por ejemplo, la funcion map crea una lista aplicando una funcion a todos los elementos de una colección (La funcion inc incrementa en uno el valor del argumento):
(map inc [0 1 2 3])
(1 2 3 4)
Las funciones están definidas por cinco partes principales:
defn
nombre
Un docstring describiendo la funcion (opcional)
Parametros listados en brackets
Cuerpo de la funcion
Arity es el número de parametros que tiene una funcion, estos parametros pueden ser de cualquier tipo, se puede tener una funcion con distintos cuerpos que corren dependiendo del arity como por ejemplo:
(defn multi-arity ;; 3-arity arguments and body ([first-arg second-arg third-arg] (do-things first-arg second-arg third-arg))
;; 2-arity arguments and body ([first-arg second-arg] (do-things first-arg second-arg))
;; 1-arity arguments and body ([first-arg] (do-things first-arg)))
Con arity overloading podemos proveer valores por defecto a una funcion, como por ejemplo:
(defn x-chop
"Describe the kind of chop you're inflicting on someone"
([name chop-type]
(str "I " chop-type " chop " name "! Take that!"))
([name]
(x-chop name "karate")))
(x-chop "Kanye West" "slap")
"I slap chop Kanye West! Take that!"
(x-chop "Kanye East")
"I karate chop Kanye East! Take that!"
Clojure también permite definir funciones de Arity variable mediante la inclusión de un parámetro rest indicado por un &
(defn codger-communication
[whippersnapper]
(str "Get off my lawn, " whippersnapper "!!!"))
(defn codger
[& whippersnappers]
(map codger-communication whippersnappers))
#'user/codger-communication #'user/codger
(codger "Billy" "Anne-Marie" "The Incredible Bulk")
("Get off my lawn, Billy!!!" "Get off my lawn, Anne-Marie!!!" "Get off my lawn, The Incredible Bulk!!!")
Se puede mezclar parámetros rest con parámetros normales, pero el parámetro rest tiene que ir en último lugar:
(defn favorite-things
[name & things]
(str "Hi, " name ", here are my favorite things: "
(clojure.string/join ", " things)))
(favorite-things "Doreen" "gum" "shoes" "kara-te")
#'user/favorite-things "Hi, Doreen, here are my favorite things: gum, shoes, kara-te"
Permite enlazar de forma concisa nombres a los valores dentro de una colección.
(defn my-first
[[first-thing]] ; Notice that first-thing is within a vector
first-thing)
(my-first ["oven" "bike" "war-axe"])
#'user/my-first "oven"
(defn chooser [[first-choice second-choice & unimportant-choices]] (println (str "Your first choice is: " first-choice)) (println (str "Your second choice is: " second-choice)) (println (str "We're ignoring the rest of your choices. " "Here they are in case you need to cry over them: " (clojure.string/join ", " unimportant-choices))))
; => Your first choice is: Marmalade
; => Your second choice is: Handsome Jack
; => We're ignoring the rest of your choices. Here they are in case \
you need to cry over them: Pigpen, Aquaman
También se pueden desestructurar mapas. De la misma manera que para desestructurar un vector o una lista.
(defn announce-treasure-location [{lat :lat lng :lng}] (println (str "Treasure lat: " lat)) (println (str "Treasure lng: " lng)))
(announce-treasure-location {:lat 28.22 :lng 81.33}) ; => Treasure lat: 100 ; => Treasure lng: 50
(defn announce-treasure-location [{:keys [lat lng]}] (println (str "Treasure lat: " lat)) (println (str "Treasure lng: " lng)))
El cuerpo de la función puede contener forms de cualquier tipo. Clojure retorna automáticamente la última sentecia evaluada.
(defn illustrative-function
[]
(+ 1 304)
30
"joe")
(illustrative-function)
#'user/illustrative-function "joe"
(defn number-comment
[x]
(if (> x 6)
"Oh my gosh! What a big number!"
"That number's OK, I guess"))
(number-comment 5)
(number-comment 7)
#'user/number-comment "That number's OK, I guess" "Oh my gosh! What a big number!"
En Clojure, las funciones no necesitan tener nombres. Se pueden crear de dos maneras. La primera es utilizar la funcion fn: (fn [param-list] function body)
(map (fn [name] (str "Hi, " name))
["Darth Vader" "Mr. Magoo"])
("Hi, Darth Vader" "Hi, Mr. Magoo")
((fn [x] (* x 3)) 8)
24
Incluso se puede asociar una función anónima con un nombre.
(def my-special-multiplier (fn [x] (* x 3)))
(my-special-multiplier 12)
#'user/my-special-multiplier 36
Clojure también ofrece otra forma, más compacta para crear funciones anónimas. Esto es como una función anónima se vería: #(* % 3)
(#(* % 3) 8)
24
(map #(str "Hi, " %)
["Darth Vader" "Mr. Magoo"])
("Hi, Darth Vader" "Hi, Mr. Magoo")
el signo de porcentaje %, indica el argumento pasado a la función. Si la función anónima toma varios argumentos, puede distinguirlos así: %1, %2, %3, ..., % Es equivalente a 1%:
(#(str %1 " and " %2) "cornbread" "butter beans")
"cornbread and butter beans"
(#(identity %&) 1 "blarg" :yip)
(1 "blarg" :yip)
Tambien se puede tener un parametro rest con %& (La funcion Identity devuelve el argumento que se le da sin alterarlo)
(#(identity %&) 1 "blarg" :yip)
(1 "blarg" :yip)
Tenemos un mapa con el cuerpo de un Hobbit pero el cuerpo es simétrico y solo tenemos la parte izquierda del Hobbit, creemos una función para arreglar esto.
(def asym-hobbit-body-parts [{:name "head" :size 3}
{:name "left-eye" :size 1}
{:name "left-ear" :size 1}
{:name "mouth" :size 1}
{:name "nose" :size 1}
{:name "neck" :size 2}
{:name "left-shoulder" :size 3}
{:name "left-upper-arm" :size 3}
{:name "chest" :size 10}
{:name "back" :size 10}
{:name "left-forearm" :size 3}
{:name "abdomen" :size 6}
{:name "left-kidney" :size 1}
{:name "left-hand" :size 2}
{:name "left-knee" :size 2}
{:name "left-thigh" :size 4}
{:name "left-lower-leg" :size 3}
{:name "left-achilles" :size 1}
{:name "left-foot" :size 2}])
#'user/asym-hobbit-body-parts
La siguinete funcion recibe el mapa anterior, y agrega la corespondoente parte derecha de cada parte izquierda.
(defn matching-part
[part]
{:name (clojure.string/replace (:name part) #"^left-" "right-")
:size (:size part)})
(defn symmetrize-body-parts
"Expects a seq of maps that have a :name and :size"
[asym-body-parts]
(loop [remaining-asym-parts asym-body-parts
final-body-parts []]
(if (empty? remaining-asym-parts)
final-body-parts
(let [[part & remaining] remaining-asym-parts]
(recur remaining
(into final-body-parts
(set [part (matching-part part)])))))))
#'user/matching-part #'user/symmetrize-body-parts
(symmetrize-body-parts asym-hobbit-body-parts)
Resultado
{:name "left-eye", :size 1}
{:name "right-eye", :size 1}
{:name "left-ear", :size 1}
{:name "right-ear", :size 1}
{:name "mouth", :size 1}
{:name "nose", :size 1}
{:name "neck", :size 2}
{:name "left-shoulder", :size 3}
{:name "right-shoulder", :size 3}
{:name "left-upper-arm", :size 3}
{:name "right-upper-arm", :size 3}
{:name "chest", :size 10}
{:name "back", :size 10}
{:name "left-forearm", :size 3}
{:name "right-forearm", :size 3}
{:name "abdomen", :size 6}
{:name "left-kidney", :size 1}
{:name "right-kidney", :size 1}
{:name "left-hand", :size 2}
{:name "right-hand", :size 2}
{:name "left-knee", :size 2}
{:name "right-knee", :size 2}
{:name "left-thigh", :size 4}
{:name "right-thigh", :size 4}
{:name "left-lower-leg", :size 3}
{:name "right-lower-leg", :size 3}
{:name "left-achilles", :size 1}
{:name "right-achilles", :size 1}
{:name "left-foot", :size 2}
{:name "right-foot", :size 2}]
let asigna valores a nombres dentor de un scope nuevo.
(let [x 3] x)
3
(def dalmatian-list
["Pongo" "Perdita" "Puppy 1" "Puppy 2"])
(let [dalmatians (take 2 dalmatian-list)]
dalmatians)
#'user/dalmatian-list ("Pongo" "Perdita")
(def x 0)
(let [x (inc x)] x)
#'user/x 1
(let [[pongo & dalmatians] dalmatian-list]
[pongo dalmatians])
["Pongo" ("Perdita" "Puppy 1" "Puppy 2")]
into permite agregar alementos a un vector desde un set.
(into [] (set [:a :a]))
[:a]
La siguiente funcion se puede ver como si Cloujure creara una funcion anonima con un parametro iteracion y recur permite llamar la función desde sí misma, pasando el argumento (inc iteration)
(loop [iteration 0] (println (str "Iteration " iteration)) (if (> iteration 3) (println "Goodbye!") (recur (inc iteration) ) ) )
; => Iteration 0
; => Iteration 1
; => Iteration 2
; => Iteration 3
; => Iteration 4
; => Goodbye!
(defn recursive-printer ([] (recursive-printer 0))
([iteration] (println iteration) (if (> iteration 3) (println "Goodbye!") (recursive-printer (inc iteration))))) (recursive-printer)
La notación literal de una expresión regular es colocar la expresión entre comillas detrás de un numeral: #"regular-expression" la funcion re-find permite establecer si una cadena cumple cierta expresion. El caracter ^ mira si la expresion se encuentra en el inicio de la cadena
(re-find #"^left-" "left-eye")
; => "left-"
(re-find #"^left-" "cleft-chin")
; => nil
(re-find #"^left-" "wongleblart")
"left-" nil nil
(defn matching-part
[part]
{:name (clojure.string/replace (:name part) #"^left-" "right-")
:size (:size part)})
(matching-part {:name "left-eye" :size 1})
#'user/matching-part {:name "right-eye", :size 1}
(matching-part {:name "head" :size 3})
{:name "head", :size 3}
El patrón de procesar cada elemento de una secuencia y retornar un resultado es tan común que hay una función integrada para ello que se llama reduce:
(reduce + [1 2 3 4])
10
Esto es equivalente a
(+ (+ (+ 1 2) 3) 4)
10
reduce toma los dos primeros valores de la secuencia, aplica la funcion, y el resultado lo vuelve a ejecutar con el siguinente elemento para cada uno de la secuencia. reduce tambien puede tomar un valor inicial, por lo cual al inicio, ya no se toman los dos primeros elementos, si no, solo el primero:
(reduce + 15 [1 2 3 4])
25
La funcion symmetrize-body-parts se puede escribir mucho más corta y expresivamente con reduce:
(defn better-symmetrize-body-parts
"Expects a seq of maps that have a :name and :size"
[asym-body-parts]
(reduce (fn [final-body-parts part]
(into final-body-parts (set [part (matching-part part)])))
[]
asym-body-parts))
#'user/better-symmetrize-body-parts
La siguiente funcion golpea una parte del cuerpo de un Hobbit aleatoriamente. cada parte del cuerpo tendra un rango de tiro que es igual al tamaño de la parte del cuerpo, se escoge una numero aleatorio (target) entre la suma del tamaño de todas las partes y se va sumando el tamaño de partes aleatorias, cuando esta suma es mayor al target, es porque esa parte del cuerpo está en el rango de tiro y ah sido golpeada.
(defn hit
[asym-body-parts]
(let [sym-parts (better-symmetrize-body-parts asym-body-parts)
body-part-size-sum (reduce + (map :size sym-parts))
target (rand body-part-size-sum)]
(loop [[part & remaining] sym-parts
accumulated-size (:size part)]
(if (> accumulated-size target)
part
(recur remaining (+ accumulated-size (:size (first remaining))))))))
#'user/hit
(hit asym-hobbit-body-parts)
{:name "right-shoulder", :size 3}
(hit asym-hobbit-body-parts)
{:name "left-forearm", :size 3}
(hit asym-hobbit-body-parts)
{:name "chest", :size 10}