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
falseynilson falsos. Todo lo demás es verdadero. -
ifes el cruce básico: condición, entonces, si-no. -
whenactúa solo si la condición se cumple. -
condevalúa varias condiciones en orden. -
casecompara contra constantes de forma eficiente. -
loop/recurreemplazan a los bucles sin consumir memoria.
Ejercicios
-
Escribe una función que reciba un número y devuelva
"positivo","negativo"o"cero". -
Usa
casepara crear un traductor de días de la semana: recibe"lunes"y devuelve"Monday", etc. -
Usa
loop/recurpara calcular el factorial de un número.
Este trabajo está bajo una licencia Attribution-NonCommercial-NoDerivatives 4.0 International.
Apóyame en Ko-fi