9. Funciones

Una de las herramientas más elementales en cualquier lenguaje que se precie es la de poder empaquetar una serie de instrucciones que serán invocadas tantas veces como creas oportuno. Un mecanismo trivial que a su vez es tan genial que nos permite crear todo un paradigma a partir de esta premisa: la programación funcional. Pero no acaba aquí su potencial ya que estirando el chicle podemos acabar creando una muñeca de Matrioshka donde una función llama a otras funciones, o la posibilidad de personalizar las variables de su interior con otros valores o funciones (parámetros). Y por si no fuera poco complejo… ¡las funciones pueden llamarse a sí mismas (recursividad)!

Trabajar con una base de funciones otorga unas ventajas que son difíciles de ignorar.

  • Reduces las repeticiones de código.

  • Creas subtareas sencillas de desarrollar.

  • Aumentas la reutilización.

  • Mejoras la legibilidad.

  • Simplificas la trazabilidad.

  • Quitas complejidad en el testing.

Por lo que es comprensible su alta utilización en las ciencias de la computación o la corriente de desarrolladores seniors que hace buen uso de las funciones.

La sintaxis en Clojure contiene la siguiente estructura.

;;    Nombre  Parámetros      Cuerpo
;;    ------  ----------  --------------
(defn saludo  [nombre]  (str "Hola " nombre))

Si deseamos ejecutarlo.

(saludo "Maese Nicolás")

;; "Hola Maese Nicolás"

¿Por qué la función ha devuelto un texto? No se lo he indicado en ningún sitio esta acción. Ello se debe a que por defecto el último valor de la función siempre será un return. En otras palabras: todas las funciones devolverán algún contenido, lo que dejes en la última instrucción.

En caso de no necesitar ningún parámetro podemos ignorar el contenido de los corchetes ([]).

; Declaramos
(defn despedida [] (str "Dios haga a vuestra merced muy venturoso caballero"))

; Ejecutamos
(despedida)

;; "Dios haga a vuestra merced muy venturoso caballero"

Como puedes comprobar, ha devuelto la única instrucción como si hubiéramos colocado un return.

Pero en la situación de precisar parámetros de entrada solo hay que incluir dentro de corchetes ([]) todas las variables a modificar.

; Declaramos
(defn halagar [prenda] (str "Un precioso " prenda " mi señor."))

; Ejecutamos
(halagar "casco")

;; "Un precioso casco mi señor."

O varios parámetros de entrada.

; Declaramos
(defn halagar [prenda rango] (str "Un precioso " prenda ", " rango  "."))

; Ejecutamos
(halagar "sombrero" "escudero")

;; "Un precioso sombrero, escudero."

Cuando debas utilizar variables en su interior, de cualquier tipo, su pareja de baile es sin duda let. Al ser de ámbito local su influencia quedará atrapada dentro de la función evitando efectos colaterales con otras instrucciones del código.

(defn sumar [num1 num2]
  "Suma num1 y num2"
  (let [resultado (+ num1 num2)]
  ;; Devolvemos el resultado
  resultado)) ; Return

(sumar 5 2)
;; 7

Subamos un poco el nivel creando una función que nos ayude a formatear nombres. No es necesario que comprendas cada elemento.

(defn formatear-con-apellido-delante [nombre-completo]
  "Formatea siguiendo el patrón apellido + coma + nombre.
   Alonso Quijano -> Quijano, Alonso
  "
  (let [nombres-separados (clojure.string/split nombre-completo #" ") ; Separa por espacios
        nombre (first nombres-separados) ; Primer elemento de la lista
        apellido (last nombres-separados)] ; Segundo elemento de la lista
    (str apellido ", " nombre))) ; Return

(formatear-con-apellido-delante "Sancho Panza") ; Ejecuto
;; "Panza, Sancho"

Multiargumentos

Las funciones pueden ser realmente flexibles cuando declaramos sus parámetros de entrada. Incluso la lógica puede estar separada dependiendo del número de parámetros que podemos recibir. Lo que en otros lenguajes sería indispensable el uso de condicionales, en Clojure es parte de cómo se declara una función.

Ilustremos el funcionamiento con un ejemplo. Quiero una función que me calcule la potencia de un número (un número multiplicado por sí mismo, como 4 x 4 = 16 que sería equivalente a 4 a la potencia de 2), pero además que admita las siguientes posibilidades:

  • Sin parámetros: devolverá un nulo.

  • 1 parámetro: calculará su potencia a 2 (5 x 5 = 25).

  • 2 parámetros: calculará su potencia por el segundo parámetro. Si es ’2’ el primer parámetro y ’4’ el segundo haremos el cálculo ’2 * 2 * 2 * 2 = 16’.

Empecemos con la estructura. Es importante que observes con atención dónde empieza y acaba cada paréntesis para que no te pierdas.

(defn calcular-potencia
  ([]     ...)         ; Sin parámetros
  ([num]  ...)         ; 1 parámetro
  ([num pow]  ...))    ; 2 parámetros

La función engloba las 3 posibles lógicas. Veamos cómo se podría implementar.

(defn calcular-potencia
  "Calcula la potencia
   num - Número a potenciar
   pow - Potencia"
  ([] nil)
  ([num] (Math/pow num 2))
  ([num pow] (Math/pow num pow)))

(calcular-potencia)
;; nil

(calcular-potencia 3)
;; 9

(calcular-potencia 2 4)
;; 16

Argumentos infinitos

Al utilizar & seguido de un alias como parámetros de entrada, marcamos que pueden darnos todos los argumentos que se precisen.

(defn contar-productos-lista
    [& lista]
    (count lista)) ; Cuenta el número de elementos de la lista

(contar-productos-lista "patatas" "cebollas" "pan")
;; 3

Otro ejemplo.

(defn resaltar-nombre
    [& nombres]
    (clojure.string/upper-case (first nombres)))

(resaltar-nombre "Miguel" "de" "Cervantes")
;; "MIGUEL"

Pero solo si usamos el prefijo &, lo cual significa que podemos usar otros parámetros fijos sin problemas.

(defn sumar-con-titulo
    [titulo & numeros] ; 'titulo' es un parámetro sencillo. 'numeros' una lista.
    (str titulo (apply + numeros)))

(sumar-con-titulo "El resultado es: " 10 20 30)
;; "El resultado es: 60"

Anónimas

Cuando creamos una función con defn

(defn saludo [nombre] (str "Hola " nombre))

en realidad estamos creando una función anónima como la siguiente.

(fn [nombre] (str "Hola " nombre))

Que a continuación guardamos en una variable para no perderla.

(def saludo (fn [nombre] (str "Hola " nombre)))

Por lo que defn es una contracción de def y fn.

;;   parámetros       cuerpo
;;  ------------  ----------------
(fn   [nombre]  (str "Hola " nombre))

Una versión corta de expresar una función anónima es con #().

#(+ 6 %)

Equivalente a:

(fn [x] (+ 6 x))
  • #( ) sustituye a (fn ).

  • % sustituye al primer parámetro de entrada, y único en el ejemplo: [x].

En caso de necesitar más de un parámetro de entrada, recurriremos a % + secuencia numérica.

#(+ %1 %2)

Equivale a:

(fn [x y] (+ x y))

O si queremos volver a utilizar argumentos infinitos.

(fn [& lista] (count lista))

Equivale a:

#(count %&)

Muchos desarrolladores avanzados usan la versión que acabas de aprender con #() porque ya conocen cuándo utilizarlo. Mi consejo es que no empieces el molino por el tejado, usa (fn) dado que visualmente queda más claro. El código debe leerse por humanos.

Resumen

  • Podemos recibir o devolver otras funciones.

  • Las funciones se declaran con defn.

  • let es tu mejor amigo, aislando las variables en el interior.

  • La última variable dentro de una función será equivalente a un return.

  • Diferente cantidad de argumentos, dentro de la misma función, pueden ser utilizados para definir diversos flujos.

  • Los argumentos infinitos se logran por & + un alias.

  • Las funciones anónimas se construyen con #() o (fn).

Ejercicios

  1. Crea una función que nos cuente la cantidad de letras que posee una palabra.
(defn contar-letras
  [palabras]
  ;; código
  )

(contar-letras "Caballo")
;; 7

(contar-letras "Asno")
;; 4
  1. Crea una función que transforme y formatee un número de centímetros a metros.
(defn formatear-a-metros
  [num]
  ;; código
  )

(formatear-a-metros 231) ; Centímetros
;; 2.31 m

(formatear-a-metros 178) ; Centímetros
;; 1.78 m
  1. Crea una función que capitalice todas las palabras que sean pasadas como parámetros.
(defn capitalizar
  [& palabras]
  ;; código
  )

(capitalizar "toledo" "consuegra")
;; ["Toledo" "Consuegra"]

This work is under a Attribution-NonCommercial-NoDerivatives 4.0 International license.

Will you buy me a coffee?

Visitors in real time

You are alone: 🐱