Ir al contenido

L3: Jerarquías y Control de Flujo Exhaustivo

Si vienes de Java, C o JavaScript, seguramente estás familiarizado con la sentencia switch. Históricamente, el switch ha sido una de las estructuras de control más propensas a errores. ¿Por qué? Por culpa del Fall-through.

En esos lenguajes, si olvidas escribir la palabra clave break; al final de un caso, el programa continuará ejecutando el siguiente caso hacia abajo, causando bugs lógicos gravísimos y muy difíciles de rastrear.

Kotlin decidió acabar con este problema de raíz eliminando por completo la palabra switch e introduciendo una estructura inmensamente más poderosa y segura: when.


En Kotlin, estructuras como if y when no son simples “órdenes” (statements), sino Expresiones (expressions). Esto significa que pueden devolver un valor.

En Java, si querías asignar un valor dependiendo de una condición, tenías que crear una variable mutable y modificarla dentro del if o del switch. En Kotlin, puedes asignar el resultado de un when directamente a una constante (val).

// Asignamos el resultado de toda la evaluación 'when' directamente a la variable.
// No hay necesidad de crear variables 'var' mutables intermedias.
val selectedOption: MenuOption = when (rawOption) {
"1" -> MenuOption.CHAT
"2" -> MenuOption.SETTINGS
"3" -> MenuOption.EXIT
else -> MenuOption.UNKNOWN
}

¿Cómo funciona when en las entrañas de la JVM (Java Virtual Machine)? El compilador de Kotlin es muy inteligente y optimiza la traducción a bytecode de Java dependiendo de cómo uses el when:

  1. Pocas Ramas: Si tu when solo tiene 2 o 3 opciones, el compilador lo traduce a una simple cadena de instrucciones if-else if.
  2. Múltiples Ramas o Enums: Si tu when tiene muchos casos, o si estás evaluando un enum class, el compilador activa el modo turbo y genera instrucciones de Java Bytecode optimizadas:
    • TABLESWITCH: Se usa si las claves son contiguas. Ofrece un acceso en memoria de tiempo constante ($O(1)$).
    • LOOKUPSWITCH: Se usa si las claves están dispersas. Realiza una búsqueda binaria garantizando un tiempo de acceso de $O(\log n)$.

🧠 Puente Conceptual: Control de Flujo y Pattern Matching en tu Lenguaje

Sección titulada «🧠 Puente Conceptual: Control de Flujo y Pattern Matching en tu Lenguaje»
  • En JS/TS clásico, la sentencia switch sufre del peligroso fall-through si olvidas break;, y no es una expresión (no devuelve un valor que puedas asignar directamente a una variable).
  • Para simular pattern matching en TypeScript, se utilizan uniones discriminadas (Discriminated Unions como type Action = { type: 'chat' } | { type: 'exit' }) evaluadas en un switch(action.type) o librerías externas como ts-pattern.
  • En Kotlin, when soporta expresiones nativas, descarta el break por diseño y realiza Smart Casts automáticos dentro de cada rama sin necesidad de librerías externas.

Antes de saltar al laboratorio, necesitamos una herramienta más: el bucle while.

Dado que en Kotlin el clásico bucle for se reserva casi exclusivamente para recorrer colecciones o listas (que veremos en la Lección 7), cuando necesitamos mantener una aplicación viva de forma interactiva (como el Game Loop de un videojuego o el menú de nuestra CLI), usamos un bucle while.

// Mantiene la aplicación ejecutándose en un ciclo infinito
// hasta que explícitamente usemos la palabra reservada 'break'.
while (true) {
// 1. Mostrar menú
// 2. Leer input
// 3. Evaluar input con 'when'
}

El Superpoder: Exhaustividad (Certeza del Compilador)

Sección titulada «El Superpoder: Exhaustividad (Certeza del Compilador)»

La verdadera magia de Kotlin no es solo que when sea bonito, sino su concepto de Control de Flujo Exhaustivo.

Cuando utilizas when como una expresión (es decir, devolviendo un valor) o cuando evalúas una “jerarquía cerrada”, el compilador te obliga matemáticamente a manejar TODAS las posibilidades. Si se te olvida un caso, el código simplemente no compilará.

Para sacarle jugo a este superpoder, Kotlin nos ofrece las Jerarquías Cerradas:

Se utilizan para representar un conjunto fijo y constante de valores. Piensa en ellos como un interruptor de opciones absolutas (ej. Días de la semana, Estados de un pedido, Opciones de menú).

// Definimos un conjunto estricto de opciones válidas para nuestra aplicación.
enum class MenuOption {
CHAT,
SETTINGS,
EXIT,
UNKNOWN
}

Si le pasamos un MenuOption a un when, Kotlin nos exigirá escribir una rama para CHAT, SETTINGS, EXIT y UNKNOWN. Si el día de mañana agregas la opción HELP al enum, ¡el compilador romperá tu código en todos los when donde olvidaste manejar HELP! Esto hace que refactorizar sea increíblemente seguro.

2. Clases Selladas (sealed class y sealed interface)

Sección titulada «2. Clases Selladas (sealed class y sealed interface)»

Son como los Enums, pero con esteroides. Mientras que un enum solo tiene valores constantes (como botones estáticos), una clase sellada permite que cada opción tenga su propio estado y datos. Por ejemplo, un estado de red podría ser Error(val exception: Exception) o Success(val data: String). Profundizaremos en ellas en la Lección 10: Companion Objects and Object Declarations.


Laboratorio Incremental: Menú Principal y while

Sección titulada «Laboratorio Incremental: Menú Principal y while»

Hoy llevaremos nuestro Chat CLI al siguiente nivel. Envolveremos el saludo de la Lección 2 dentro de un bucle infinito y presentaremos un menú interactivo. Parsearemos la entrada de texto cruda hacia nuestro propio enum class para garantizar un control de flujo exhaustivo.

Nota: A partir de esta lección, debes escribir todo tu código fuente, variables y enums en Inglés.

Hasta arriba de tu archivo Main.kt (fuera de la función main), declara un enum para restringir matemáticamente las opciones de nuestro menú.

// Definimos un Enum para representar las opciones de nuestro menú de forma estricta.
enum class MenuOption {
CHAT,
SETTINGS,
EXIT,
UNKNOWN
}

Justo debajo de donde imprimimos Hello, $username!, abre un bloque while (true) e imprime las opciones del menú.

// Bucle principal (Main Loop) que mantiene viva la aplicación interactiva
while (true) {
println("\n--- Main Menu ---")
println("1. Chat")
println("2. Settings")
println("3. Exit")
print("Choose an option: ")
val rawOption: String? = readlnOrNull()
// Continuaremos en el Paso 3 aquí adentro...
}

Paso 3: Parseo seguro usando when como expresión

Sección titulada «Paso 3: Parseo seguro usando when como expresión»

Adentro del while, debajo del readlnOrNull(), usaremos un when para convertir la cadena de texto cruda "1" en nuestra constante segura MenuOption.CHAT. Fíjate cómo asignamos el resultado directamente a val selectedOption.

// Usamos 'when' como expresión para mapear el input crudo (String)
// a nuestro Enum estructurado, devolviendo el valor directamente.
val selectedOption: MenuOption = when (rawOption) {
"1" -> MenuOption.CHAT
"2" -> MenuOption.SETTINGS
"3" -> MenuOption.EXIT
else -> MenuOption.UNKNOWN
}

Ahora usaremos nuestro Enum estructurado para ejecutar la lógica de la aplicación. Escribe este segundo when debajo del anterior:

// Usamos 'when' nuevamente, pero esta vez con nuestro Enum. Al ser una jerarquía
// cerrada (el compilador conoce todas las opciones), nos obliga a manejar
// exhaustivamente todos los casos posibles sin necesidad de un 'else'.
when (selectedOption) {
MenuOption.CHAT -> {
println(">>> Entering chat mode... (Work in progress)")
}
MenuOption.SETTINGS -> {
println(">>> Entering settings... (Work in progress)")
}
MenuOption.EXIT -> {
println("Goodbye, $username! See you soon.")
break // Rompe el bucle while(true) y finaliza el programa
}
MenuOption.UNKNOWN -> {
println("Invalid option. Please try again.")
}
}

Paso 5: ¡Ejecuta y prueba tu menú en tu entorno preferido!

Sección titulada «Paso 5: ¡Ejecuta y prueba tu menú en tu entorno preferido!»

Puedes probar tu menú interactivo según la herramienta que estés utilizando:

  • En Android Studio / IntelliJ IDEA: Presiona el botón verde de Play (▶️) junto a fun main() y escribe en la pestaña inferior Run.
  • En Google Antigravity / Visual Studio Code: Presiona el atajo Cmd + Shift + B (macOS) o Ctrl + Shift + B (Windows/Linux) para abrir la terminal interactiva integrada.
  • En Terminal de comandos: Ejecuta:
    Ventana de terminal
    ./gradlew run --console=plain -q
    (En Windows: gradlew.bat run --console=plain -q)

Casos de prueba a verificar:

  1. Después de ingresar tu nombre, el menú debe desplegarse con las 3 opciones.
  2. Escribe 1 y presiona Enter: debe imprimir el mensaje de chat y volver a presentarte el menú (gracias a la persistencia del bucle while).
  3. Escribe una opción inexistente (por ejemplo "9" o "hola"): el caso exhaustivo MenuOption.UNKNOWN atrapará el error e imprimirá "Invalid option. Please try again.".
  4. Escribe 3: el comando break romperá el bucle while (true) y finalizará el programa despidiéndose con tu nombre.

¡Felicidades! Has construido la columna vertebral interactiva de nuestra aplicación usando los principios de control de flujo exhaustivo.



¿Tienes alguna duda sobre when exhaustivo, el bytecode de la JVM o el bucle del menú?