#!/usr/bin/env python # coding: utf-8 # # Sintaxis # ## Forms # 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: # In[6]: 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: # In[11]: (+ 1 2 3) (str "It was the panda " "in the library " "with a dust buster") # ## Declarar variable # In[15]: (def a 10) a # ## Control Flow # ### if # # Forma general del if: # # (if boolean-form # then-form # optional-else-form) # In[13]: (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!") # ### do # # El operador do permite agrupar múltiples formas, y ejecutar cada una de ellas: # In[10]: (if true (do (def a 1) (def b 2) (+ a b) ) (do (println "Failure!") "By Aquaman's trident!")) # ## When # # El operador When es como una combinación de if y do, pero sin la rama else. Ejemplo: # In[23]: (when true (def a "Success! ") (str a "abra cadabra") ) # ## Expresiones booleanas # # 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? # In[24]: (nil? 1) (nil? nil) # Tanto nil y false se utilizan para representar falsedad, mientras que todos los demás valores son lógicamente verdaderos. # In[25]: (if "bears eat beets" "bears beets Battlestar Galactica") # In[26]: (if nil "This won't be the result because nil is falsey" "nil is falsey") # EL operador de igualdad es = # In[29]: (= 1 1) (= nil nil) (= "hola" "Hola") (= 1 "1") # ### Operadores and y or # or retorna el primer valor verdadero o si no, el último valor. # In[47]: (or false nil :large_I_mean_venti :why_cant_I_just_say_large) # In[48]: (or (= 0 1) (= "yes" "no")) # and retorna el primer valor verdadero o si no, el ultimo valor verdadero. # In[50]: (and :free_wifi :hot_coffee) # In[51]: (and :feelin_super_cool nil false) # ## Data Structures # # Todas las estructuras de datos de Clojure son inmutables. # Integer, float, ratio, strings. # In[55]: 93 1.2 1/5 (println "\"Great cow of Moscow!\" - Hermes Conrad") # 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: # In[62]: (def name "Chewbacca") (str "\"Uggllglglglglglglglll\" - " name) # ### Maps # # # En este ejemplo :first-name and :last-name are keywords, un diccionario se declara con {} # In[1]: {:first-name "Charlie" :last-name "McFishwich"} # Aquí asociamos la llave "string-key" con la funcion + # In[2]: {"string-key" +} # Los mapas pueden ser anidados # In[3]: {:name {:first "John" :middle "Jacob" :last "Jingleheimerschmidt"}} # También se puede usar la función hash-map # In[4]: (hash-map :a 1 :b 2) # Para acceder a un valor se usa la función get # In[5]: (get {:a 0 :b 1} :b) # In[6]: (get {:a 0 :b {:c "ho hum"}} :b) # si no se encuentra el valor, se retorna nil o se puede dar un valor por defecto como "unicorns?" # In[7]: (get {:a 0 :b 1} :c) (get {:a 0 :b 1} :c "unicorns?") # La funcion get-in permite buscar en mapas anidados # In[8]: (get-in {:a 0 :b {:c "ho hum"}} [:b :c]) # Otra manera de buscar un valor, es usar el mapa como una funcion con la llave como argumento # In[10]: ({:name "The Human Coffeepot"} :name) # ## Keywords # Pueden ser usadas como funciones que buscan en un mapa su correspondiente valor. ejemplos de keywords: # :a # :rumplestiltsken # :34 # :_? # In[9]: (:a {:a 1 :b 2 :c 3}) # ## Vectors # Obtener el elemento en el indice 0 # In[12]: (get [3 2 1] 0) # Obtener el elemento en el indice 1 # In[13]: (get ["a" {:name "Pugsley Winterbottom"} "c"] 1) # Se pueden crear vectores con la función vector # In[14]: (vector "creepy" "full" "moon") # Se pueden añadir elementos al final de un vector con la funcion conj # In[15]: (conj [1 2 3] 4) # ## Lists # 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. # In[16]: '(1 2 3 4) # Para obtener un valor, se usa la funcion nth # In[17]: (nth '(:a :b :c) 0) (nth '(:a :b :c) 2) # Las listas, al igual que los vectores pueden contener cualquier tipo de valor, tambien se pueden declarar con la funcion list. # In[18]: (list 1 "two" {3 4}) # Se pueden insertar elementos al comienzo de la lista con conj # In[19]: (conj '(1 2 3) 4) # ## Sets # Los sets con coleciones de valores únicos, una notacion literal para un set puede ser: # In[20]: #{"kurt vonnegut" 20 :icicle} # Tambien se puede usar la función hash-set # In[21]: (hash-set 1 1 2 2) # Si se agraga un valor a un set que ya se encuentra, este se rechaza: # In[23]: (conj #{:a :b :d} :b) # In[24]: (conj #{:a :b :d} :c) # Se pueden crear sets a partir de listas o vectores ya creados # In[25]: (set [3 3 3 4 4]) # Se puede mirar si un set contienen un elemento con la funcion get, un keyword o la funcion contains? # In[26]: (contains? #{:a :b} :a) (contains? #{:a :b} 3) # In[84]: (:a #{:a :b}) (:c #{:a :b}) # In[28]: (get #{:a :b} :a) (get #{:a :b} "kurt vonnegut") # ## Calling Functions # Esta es una expresion que retorna la funcion + # In[29]: (or + -) # Ejemplos de expresiones que retornan 6: # In[30]: ((or + -) 1 2 3) # In[31]: ((and (= 1 1) +) 1 2 3) # In[32]: ((first [+ 0]) 1 2 3) # 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): # In[33]: (map inc [0 1 2 3]) # ## Defining Functions # 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 # 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: # In[ ]: (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"))) # In[37]: (x-chop "Kanye West" "slap") # In[38]: (x-chop "Kanye East") # Clojure también permite definir funciones de Arity variable mediante la inclusión de un parámetro rest indicado por un & # In[41]: (defn codger-communication [whippersnapper] (str "Get off my lawn, " whippersnapper "!!!")) (defn codger [& whippersnappers] (map codger-communication whippersnappers)) # In[42]: (codger "Billy" "Anne-Marie" "The Incredible Bulk") # Se puede mezclar parámetros rest con parámetros normales, pero el parámetro rest tiene que ir en último lugar: # In[43]: (defn favorite-things [name & things] (str "Hi, " name ", here are my favorite things: " (clojure.string/join ", " things))) (favorite-things "Doreen" "gum" "shoes" "kara-te") # ## Destructuring # Permite enlazar de forma concisa nombres a los valores dentro de una colección. # In[44]: (defn my-first [[first-thing]] ; Notice that first-thing is within a vector first-thing) (my-first ["oven" "bike" "war-axe"]) # (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))) # ## Function Body # El cuerpo de la función puede contener forms de cualquier tipo. Clojure retorna automáticamente la última # sentecia evaluada. # In[48]: (defn illustrative-function [] (+ 1 304) 30 "joe") (illustrative-function) # In[49]: (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) # ## Anonymous Functions # 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) # In[50]: (map (fn [name] (str "Hi, " name)) ["Darth Vader" "Mr. Magoo"]) # In[51]: ((fn [x] (* x 3)) 8) # Incluso se puede asociar una función anónima con un nombre. # In[52]: (def my-special-multiplier (fn [x] (* x 3))) (my-special-multiplier 12) # 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) # In[54]: (#(* % 3) 8) # In[55]: (map #(str "Hi, " %) ["Darth Vader" "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%: # In[56]: (#(str %1 " and " %2) "cornbread" "butter beans") # In[57]: (#(identity %&) 1 "blarg" :yip) # Tambien se puede tener un parametro rest con %& (La funcion Identity devuelve el argumento que se le da sin alterarlo) # In[58]: (#(identity %&) 1 "blarg" :yip) # ## The Shire’s Next Top Model # 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. # In[59]: (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}]) # La siguinete funcion recibe el mapa anterior, y agrega la corespondoente parte derecha de cada parte izquierda. # In[60]: (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)]))))))) # In[ ]: (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}] # ### Funciones utilizadas. # ### let # let asigna valores a nombres dentor de un scope nuevo. # In[67]: (let [x 3] x) # In[63]: (def dalmatian-list ["Pongo" "Perdita" "Puppy 1" "Puppy 2"]) (let [dalmatians (take 2 dalmatian-list)] dalmatians) # In[64]: (def x 0) (let [x (inc x)] x) # In[68]: (let [[pongo & dalmatians] dalmatian-list] [pongo dalmatians]) # ### into # into permite agregar alementos a un vector desde un set. # In[69]: (into [] (set [:a :a])) # ### loop # 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) # ### Regular Expressions # 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 # In[71]: (re-find #"^left-" "left-eye") ; => "left-" (re-find #"^left-" "cleft-chin") ; => nil (re-find #"^left-" "wongleblart") # In[72]: (defn matching-part [part] {:name (clojure.string/replace (:name part) #"^left-" "right-") :size (:size part)}) (matching-part {:name "left-eye" :size 1}) # In[73]: (matching-part {:name "head" :size 3}) # ## reduce # 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: # In[74]: (reduce + [1 2 3 4]) # Esto es equivalente a # In[75]: (+ (+ (+ 1 2) 3) 4) # 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: # In[76]: (reduce + 15 [1 2 3 4]) # La funcion symmetrize-body-parts se puede escribir mucho más corta y expresivamente con reduce: # In[77]: (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)) # ## Hobbit Violence # 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. # In[78]: (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)))))))) # In[79]: (hit asym-hobbit-body-parts) # In[80]: (hit asym-hobbit-body-parts) # In[81]: (hit asym-hobbit-body-parts)