Funciones

  • ¿Qué son las funciones?
  • Nombrando funciones
  • [bonus] Funciones que reciben otras funciones
    • map y reduce
  • [bonus] Funciones anónimas
  • [bonus] Asignación con let

¿Qué son las funciones?

Ya has visto algunas funciones, como count, conj, first, y rest. 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
  • +, -, *, /
  • Una porción de código que recibe valores y devuelve un valor

Un ejemplo de función

  • 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 llamado turtle.
  • (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))

Cómo usar la función 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}}

Una función con múltiples argumentos

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}}

EJERCICIO 1: Mover tortugas con funciones

  1. Escribir una función
    • Ve a walk.clj
    • En el editor, escribe la función forward-right que aparece en la presentación (abajo).
    • (Optional) Guarda walk.clj
    • Selecciona la función forward-right entera y pulsa “Eval Selection”
  2. Usa una función
    • Escribe (forward-right :trinity) en el panel del REPL.
    • Repite el paso anterior al menos 8 veces (utiliza la flecha hacia arriba y pulsa Enter).
(defn forward-right
  "Mueve a la derecha a la tortuga especificada e inclina su cabeza"
  [turtle]
  (forward turtle 60)
  (right turtle 135))

EJERCICIO 2: Mover tortugas usando funciones con parámetros

  • Ve a walk.clj
  • En el editor, escribe la función forward-right-with-len-ang, que recibe tres argumentos: turtle, len, y angle (extensión de forward-right-with-len)
  • Selecciona la función forward-right-with-len-ang entera y pulsa Reload Selection.
  • En el panel del REPL, escribe (forward-right-with-len-ang :trinity 60 120)
  • Repite lo anterior, evaluando varias veces la función en el REPL.

Nombrando funciones

Los nombres son símbolos

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.

Dos clases de funciones

Clojure tiene dos clases de funciones:

  1. funciones que devuelven un valor
  2. funciones que devuelven true o false A las funciones de la segunda clase se las llama predicados.
Ejemplos de funciones predicado

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?

[Bonus ]

Funciones que reciben otras funciones

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

La función 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

La función 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

EJERCICIO 3 [BONUS]: Calcular el promedio

  • Crea una función llamada average que reciba un vector de mapas.
  • Usa [{:angle 30} {:angle 90} {:angle 50}] como vector de entrada.
  • Calcula el valor de :angle.

  • Pista: Necesitarás utilizar las funciones map, reduce y count.

[Bonus]

Funciones anónimas

Funciones sin nombre

Hasta ahora, todas las funciones que vimos tenían nombres, como +, str y reduce. 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 con fn, de esta forma:

Referencia: Anonymous Function

(fn [s1 s2] (str s1 " " s2))

vs. funciones no anónimas

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))

Ejemplos de uso de funciones anónimas

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 o reduce, 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"

[Bonus]

Asignación con 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 llamada let.

Asignando nombres a valores: let

Podemos asignar un nombre a un valor utilizando let, igual que con def. 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

Ejemplo de 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) a t2.

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 del let, t1 and t2 están definidos.

Ve a walk.clj y escribe la función opposite. Luego, evalúa la función opposite en la última línea de la definición de la función. También, evalúa el ejemplo de uso de la función opposite.

;; 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.