11. Control de flujo

En la vida de todo caballero andante llega el momento de tomar decisiones: ¿ataco al gigante o huyo del molino? ¿Es realmente Dulcinea o una campesina? En Clojure estas encrucijadas se resuelven con una particularidad fascinante: todo es una expresión que devuelve un valor. No existen sentencias vacías. Cada decisión produce algo.

Truthiness

Antes de hablar de condicionales necesitas saber qué considera Clojure verdadero y qué falso. La regla es memorable por su sencillez: solo false y nil son falsos. Todo lo demás es verdadero. Sí, incluyendo 0, cadenas vacías y colecciones vacías. Si vienes de JavaScript o Python, olvida lo que sabías.

(if true "verdadero" "falso")   ;; "verdadero"
(if false "verdadero" "falso")  ;; "falso"
(if nil "verdadero" "falso")    ;; "falso"
(if 0 "verdadero" "falso")     ;; "verdadero"
(if "" "verdadero" "falso")    ;; "verdadero"
(if [] "verdadero" "falso")    ;; "verdadero"

if

La bifurcación más básica. Como un cruce de caminos: condición, camino verdadero, camino falso.

(if (even? 4) "par" "impar")
;; "par"

Si omites el camino falso y la condición no se cumple, devuelve nil. Como preguntar al aire.

(if (even? 3) "par")
;; nil

Si necesitas ejecutar varias expresiones en una rama, usa do para agruparlas.

(if (even? 4)
  (do (println "Es par") true)
  (do (println "Es impar") false))

when

when es un if que solo tiene camino verdadero, con do implícito. Ideal cuando solo te interesa actuar si la condición se cumple, como un caballero que solo desenvaina si hay peligro.

(when (pos? 5)
  (println "¡Adelante!")
  "positivo")
;; ¡Adelante!
;; "positivo"

cond

Cuando tienes varias condiciones encadenadas, cond las evalúa en orden y ejecuta la primera que sea verdadera. :else es la cláusula por defecto, siempre verdadera.

(defn clasificar-enemigo [poder]
  (cond
    (< poder 10)  "Oveja disfrazada"
    (< poder 50)  "Bandido de poca monta"
    (< poder 100) "Caballero rival"
    :else          "¡Gigante! (o molino)"))

(clasificar-enemigo 75)
;; "Caballero rival"

case

Compara un valor contra constantes. Es más eficiente que cond cuando las opciones son fijas. Si no hay coincidencia y no proporcionas un valor por defecto, Clojure protesta lanzando una excepción.

(defn responder [orden]
  (case orden
    "atacar"  "¡En guardia!"
    "huir"    "Un caballero no huye... pero corre con estilo"
    "salir"   "Hasta pronto, escudero"
    "No entiendo esa orden"))

(responder "atacar")
;; "¡En guardia!"

loop y recur

Aquí viene uno de los giros más elegantes. En programación funcional no usamos bucles for o while. ¿Entonces cómo repetimos? Con recursión. loop define un punto de partida con valores iniciales y recur salta de vuelta a ese punto con valores nuevos. Es como Sancho volviendo una y otra vez a contar sus refranes, pero sin agotar la memoria.

(loop [i 0
       escuderos []]
  (if (< i 5)
    (recur (inc i) (conj escuderos (str "Escudero " i)))
    escuderos))
;; ["Escudero 0" "Escudero 1" "Escudero 2" "Escudero 3" "Escudero 4"]

recur también funciona dentro de defn, saltando al inicio de la función.

(defn cuenta-atras [n]
  (when (pos? n)
    (println n "...")
    (recur (dec n))))

(cuenta-atras 3)
;; 3 ...
;; 2 ...
;; 1 ...

Resumen

  • Solo false y nil son falsos. Todo lo demás es verdadero.

  • if es el cruce básico: condición, entonces, si-no.

  • when actúa solo si la condición se cumple.

  • cond evalúa varias condiciones en orden.

  • case compara contra constantes de forma eficiente.

  • loop/recur reemplazan a los bucles sin consumir memoria.

Ejercicios

  1. Escribe una función que reciba un número y devuelva "positivo", "negativo" o "cero".

  2. Usa case para crear un traductor de días de la semana: recibe "lunes" y devuelve "Monday", etc.

  3. Usa loop/recur para calcular el factorial de un número.

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: 🐱