map
y reduce
let
Ya has visto algunas funciones, como
count
,conj
,first
, yrest
. También usamos funciones en toda la arimética que hicimos hasta ahora:+
,-
,*
, y/
. Pero, ¿qué significa ser una función?
Una función es una porción de código discreta e independiente, que recibe algunos valores (llamados parámetros o argumentos), y devuelve un valor.
Referencia: Basics of Function
count
, conj
, first
+
, -
, *
, /
defn
indica que vamos a definir una función.forward-right
es el nombre de esta función.- La cadena de texto en la siguiente línea es la documentación de la función, que explica qué hace la función. Esta línea es opcional.
[turtle]
es la lista de argumentos. Aquí tenemos un argumento llamadoturtle
.(forward turtle 60) (right turtle 135)
es el cuerpo de la función. Esto es lo que se ejecuta cuando usamos la función.
(defn forward-right
"Mueve la tortuga especificada a la derecha e inclina su cabeza"
[turtle]
(forward turtle 60)
(right turtle 135))
forward-right
Para usar
forward-right
, invocamos la función, como hicimos con todas las funciones que ya hemos utilizado.
(forward-right :trinity) ;=> {:trinity {:angle 135}}
(forward-right :neo) ;=> {:neo {:angle 135}}
Las funciones también pueden tomar más de un argumento. Hagamos una función
forward-right-with-len
que además de la tortuga, reciba una distancia hacia adelante.
(defn forward-right-with-len
"Dados turtle y length, adelanta turtle e inclina su cabeza"
[turtle len]
(forward turtle len)
(right turtle 135))
(forward-right-with-len :trinity 90) ;=> {:trinity {:angle 135}}
(forward-right-with-len :neo 80) ;=> {:neo {:angle 135}}
walk.clj
forward-right
que aparece en la presentación (abajo).walk.clj
forward-right
entera y pulsa “Eval Selection”(forward-right :trinity)
en el panel del REPL.(defn forward-right
"Mueve a la derecha a la tortuga especificada e inclina su cabeza"
[turtle]
(forward turtle 60)
(right turtle 135))
walk.clj
forward-right-with-len-ang
, que recibe tres
argumentos: turtle, len, y angle (extensión de forward-right-with-len
)forward-right-with-len-ang
entera y pulsa Reload Selection.(forward-right-with-len-ang :trinity 60 120)
Los nombres de funciones son símbolos, al igual que los símbolos definidos con
def
cuando asignamos nombres a valores.
Los símbolos deben comenzar con un carácter no numérico, y pueden contener carácteres alfanuméricos, *, +, !, -, _, y ?. Esta flexibilidad es importante con las funciones, ya que hay ciertas formas de expresiones que usaremos.
Clojure tiene dos clases de funciones:
- funciones que devuelven un valor
- funciones que devuelven true o false A las funciones de la segunda clase se las llama predicados.
En Clojure,
=
es una función predicado, lo cual puede ser un hecho sorprendente. Además, como muchos otros lenguajes, Clojure tiene funciones predicado para probar mayor o igual, menor o igual, etc. Mayormente las funciones predicado terminan con?
.
=
,not=
>
,<
,>=
,<=
true?
,false?
,empty?
,nil?
,vector?
,map?
Algunas de las funciones más poderosas que puedes utilizar con colecciones pueden tomar otras funciones como argumentos. Ésta es una de las cosas más mágicas acerca de Clojure–y varios otros lenguajes de programación. Ésta es una idea complicada, así que puede que no parezca tener mucho sentido al principio. Veamos un ejemplo para aprender más sobre esto.
Referencia: Higher-order Function
map
map
es una función que recibe otra función y una colección.map
invoca la función recibida en cada elemento de la colección, y luego devuelve una nueva colección con los resultados de esas invocaciones. Éste es un concepto extraño, pero es uno de los conceptos más importantes de Clojure y de la programación funcional en general.
(map inc [1 2 3]) ;=> (2 3 4)
(map (partial + 90) [0 30 60 90]) ;=> (90 120 150 180)
Referencias: partial
reduce
Veamos otra función que recibe una función. Esta función es
reduce
, y se usa para convertir colecciones en un único valor.
reduce
recibe los dos primeros elementos de la colección que recibió, e invoca la función provista sobre esos elementos. Luego, vuelve a invocar la misma función – sólo que esta vez lo hace usando el resultado de la invocación anterior, junto con el siguiente elemento de la colección.reduce
hace esto repetidamente hasta que finalmente llega al final de la colección.
(reduce str (turtle-names)) ;=> ":trinity:neo:oracle:cypher"
(reduce + [30 60 90]) ;=> 180
average
que reciba un vector de mapas.[{:angle 30} {:angle 90} {:angle 50}]
como vector de entrada.Calcula el valor de :angle.
map
, reduce
y count
.Hasta ahora, todas las funciones que vimos tenían nombres, como
+
,str
yreduce
. Pero las funciones no necesitan nombres, de la misma manera que los valores no necesitan nombres. A las funciones que no tienen nombre las llamamos funciones anónimas. Una función anónima se crea confn
, de esta forma:
Referencia: Anonymous Function
(fn [s1 s2] (str s1 " " s2))
Antes de continuar, debes entender que siempre eres libre de nombrar tus funciones. Hacer eso no tiene nada de malo. Sin embargo, hay muchísimo código Clojure con funciones anónimas, por lo que debes ser capaz de entenderlo.
(defn join-with-space
[s1 s2]
(str s1 " " s2))
Por qué querrías usar funciones anónimas? Las funciones anónimas pueden ser muy útiles cuando tenemos funciones que reciben otras funciones, como
map
oreduce
, de las cuales ya aprendimos en la sección sobre Funciones. Veamos ejemplos de uso de funciones anónimas:
(map (fn [t] (forward t 45)) (turtle-names))
;=> ({:trinity {:length 45}} {:neo {:length 45}} {:oracle {:length
45}} {:cypher {:length 45}})
(reduce (fn [x y] (+ x y)) [1 2 3]) ;=> 6
(reduce (fn [a b] (str a ", " b)) (map name (turtle-names)))
;=> "trinity, neo, oracle, cypher"
let
Cuando estás creando funciones, puedes querer asignar nombres a valores para poder reutilizar esos valores o hacer tu código más legible. Sin embargo, dentro de una función, no deberías usar
def
como lo harías fuera de una función. En lugar de eso, deberías usar una forma especial llamadalet
.
let
Podemos asignar un nombre a un valor utilizando
let
, igual que condef
. Cuando un nombre es asignado a un valor, el nombre se llama símbolo.
Referencia: Assignment let
(let [mangoes 3
oranges 5]
(+ mangoes oranges))
;=> 8
let
Esta es la función más complicada que hemos visto hasta ahora, así que vamos paso por paso. Primero, tenemos el nombre de la función, el string de documentación, y los argumentos, igual que en otras funciones.
Luego, vemos
let
.let
recibe un vector de nombres y valores intercalados.t1
es le primer nombre, y le asignamos el resultado de(first names)
. También le asignamos el resultado de(last names)
at2
.
Luego del vector de nombres y valores, está el cuerpo del
let
. Igual que el cuerpo de una función, éste se ejecuta y devuelve un valor Dentro dellet
,t1
andt2
están definidos.
Ve a
walk.clj
y escribe la funciónopposite
. Luego, evalúa la funciónopposite
en la última línea de la definición de la función. También, evalúa el ejemplo de uso de la funciónopposite
.
;; function definition
(defn opposite
"Dada una coleción de nombres de tortugas, mueve dos de ella en direcciones diferentes."
[names]
(let [t1 (first names)
t2 (last names)]
(forward t1 40)
(backward t2 30)))
;; function usage
(opposite (turtle-names))
Vuelve a la primera diapositiva, o ve al outline del currículum.