CodeGym /Cursos /Swift SELF /if case / guard case — comprobacion puntual de un caso

if case / guard case — comprobacion puntual de un caso

Swift SELF
Nivel 26, Lección 2
Disponible

1. ¿Para que se necesitan if case y guard case?

Cuando acabas de conocer switch, parece que es la respuesta universal para todo: «tengo un enum — entonces siempre switch». Pero en tareas reales el cerebro muy pronto empieza a pedir concision: no quieres desarrollar un switch de 25 lineas si solo necesitas comprobar exactamente un caso.

Imagina que estas escribiendo una aplicacion CLI y en cada paso del bucle procesas un comando. En un lugar necesitas procesar todos los comandos (ahi switch es ideal), y en otro simplemente quieres entender si el comando es una “salida”. Precisamente para estas comprobaciones “puntuales” existen if case y guard case: es el mismo pattern matching, solo que sin la sensacion de haber sacado un tanque para mover un taburete.

Vamos a fijar una mini tabla para que veas el papel de cada herramienta:

Herramienta Cuando usarla Que estilo produce
switch
Hay que procesar todas las variantes de enum Una “ramificacion” con una vision completa del mundo
if case
Hay que comprobar un caso concreto (a veces extrayendo datos) Una comprobacion breve y puntual
guard case
Solo tiene sentido seguir si el valor es el caso necesario Salida temprana, codigo lineal sin anidamiento

2. if case: comprobaciones puntuales

Comprobamos solo el caso

Empecemos por lo mas simple: no necesitamos datos dentro del caso, solo queremos saber: «¿es esto o no?». Este escenario aparece constantemente: los comandos “ayuda”, “salir”, “mostrar lista” a menudo no tienen argumentos, pero incluso si los tienen, a veces solo te importa el hecho del caso.

La sintaxis de if case se lee como «si el valor encaja con el patron ...». Conviene recordar la forma: patron a la izquierda, valor a la derecha. Se parece mucho a switch, solo que en una unica rama.

import Foundation

enum Command {
    case add(title: String)
    case list
    case exit
}

let command: Command = .exit

if case .exit = command {
    print("Goodbye!") // Goodbye!
}

Aqui hemos comparado command con el patron .exit. El payload de .exit no existe, asi que todo es lo mas directo posible.

Un habito util: percibir if case como un «mini switch para un solo case», no como una magia separada.

if case let: extraemos associated values

Cuando el payload si es necesario, if case se vuelve aun mas interesante. Puedes comprobar el caso y sacar los datos a una constante local mediante let. Esto se parece a case let .add(title) en switch, solo que sin las demas ramas.

Importante: let se puede poner una sola vez antes del patron (if case let .add(title) = ...), y entonces todos los valores extraidos seran constantes. Resulta un poco inusual al principio, pero luego ahorra muchisima vista.

import Foundation

enum Command {
    case add(title: String)
    case list
}

let command: Command = .add(title: "Clean Code")

if case let .add(title) = command {
    print("Adding book: \(title)") // Adding book: Clean Code
}

Fijate en una idea importante: no “entramos dentro de un enum”, sino que comparamos el valor con un patron. Es decir, title aparece solo dentro del bloque if, porque solo ahi esta garantizado que el comando es realmente .add.

La coma en if case: condicion adicional

A menudo no basta con entender que es .remove(index: ...) — tambien hay que asegurarse de que el indice es razonable. En if puedes anadir condiciones adicionales con una coma, igual que en un if normal.

Esto es comodo cuando la condicion es corta y hace el codigo mas claro. Si la condicion se convierte en una demostracion matematica de 14 lineas, mejor ir a switch o a una funcion separada.

import Foundation

enum Command {
    case remove(index: Int)
}

let command: Command = .remove(index: -1)

if case let .remove(index) = command, index >= 0 {
    print("Deleting item #\(index)")
} else {
    print("Incorrect index") // Incorrect index
}

Aqui se lee casi como humano: “si el comando es remove, y el indice no es negativo, entonces eliminamos”.

_: ignoramos parte de los datos

A veces el payload contiene varios valores, pero solo necesitas uno. Entonces es util usar _ en el patron: significa “no me interesa lo que haya ahi”.

El sentido de _ es no crear una variable, no ocupar un nombre, no provocarte a ti mismo a escribir logica innecesaria.

import Foundation

enum Command {
    case move(from: Int, to: Int)
}

let command: Command = .move(from: 10, to: 20)

if case let .move(_, to) = command {
    print("Final position: \(to)") // Final position: 20
}

Aqui mostramos claramente la intencion: “from no me importa”.

Cuando if case empeora la legibilidad

if case es una herramienta excelente mientras siga siendo puntual. Pero a los principiantes a menudo se les va la mano: empiezan a construir una “escalera” de if case cuando en realidad necesitan un switch. Al final el codigo se parece a un juego de “adivina que casos existen”.

Si notas que estas comprobando tres o cuatro variantes seguidas, y dentro tienes acciones distintas, entonces switch suele leerse mejor: muestra todos los casos a la vez y da al compilador la oportunidad de verificar la exhaustividad.

Ejemplo de “demasiado if case” (contraejemplo, pero compila):

import Foundation

enum Command { case add(title: String), list, exit }
let command: Command = .list

if case .add = command { print("add") }
if case .list = command { print("list") } // list
if case .exit = command { print("exit") }

Se puede hacer, pero normalmente no apetece: son comprobaciones sueltas en vez de una ramificacion clara. En un sitio asi switch sera mas “educado” con el lector.

3. guard case: salida temprana y codigo lineal

Ahora pasamos a guard case. En sensacion se parece a guard let (que ya conoces de Optional), solo que en lugar de optional binding hace pattern matching.

La filosofia de guard es la misma: “comprueba las condiciones al principio y luego escribe el codigo de forma lineal”. Esto reduce el anidamiento y simplifica las funciones. Y si, guard te obliga a salir del alcance actual (return, break, continue) si la condicion no se cumple.

import Foundation

enum Command {
    case add(title: String)
    case list
}

func extractTitle(from command: Command) -> String? {
    guard case let .add(title) = command else { return nil }
    return title
}

print(extractTitle(from: .list) as Any)               // nil
print(extractTitle(from: .add(title: "Dune")) as Any) // Optional("Dune")

Fijate en el estilo: “descartamos” todos los comandos salvo .add, y luego trabajamos con title como con una constante normal. Eso es la “salida temprana”.

guard case + _: tomamos solo lo necesario

Al igual que en if case, en guard case puedes ignorar parte del payload. Esto es especialmente util cuando el caso contiene varios valores, pero la funcion solo responde por uno.

import Foundation

enum Command {
    case move(from: Int, to: Int)
    case stop
}

func destination(from command: Command) -> Int? {
    guard case let .move(_, to) = command else { return nil }
    return to
}

print(destination(from: .stop) as Any)                 // nil
print(destination(from: .move(from: 1, to: 5)) as Any) // Optional(5)

El estilo resulta muy “uniforme”: comprobacion al principio, y luego la “parte util” de la funcion.

Modelo mental: es el mismo pattern matching

Es muy util detenerse y “clavar” la comprension: if case y guard case no son magia aparte del lenguaje. Es el mismo mecanismo de comparacion con un patron, solo que en formas distintas.

Incluso puedes traducirlo mentalmente:

  • if case PATTERN = value { ... } aproximadamente a “mini switch con una sola rama”
  • guard case PATTERN = value else { return } aproximadamente a “comprobacion de precondicion al inicio”

Historicamente, cuando los associated values se percibian como tupla, las comparaciones podian parecer extrañas. Ahora el lenguaje es mas regular, pero el rastro de esa idea en las discusiones y ejemplos sigue apareciendo, y por eso a veces a los principiantes les parece que los patrones viven su propia vida.

Si mantienes en la cabeza “esto es solo una comparacion”, todo se vuelve mas facil.

Diagrama de flujo: como pensar en guard case

A veces es mejor percibir guard case no como sintaxis, sino como un pequeno algoritmo de filtrado.

flowchart TD
    A[Hay un valor Command] --> B{¿Es el caso necesario?}
    B -- no --> C[Salida temprana: return/break/continue]
    B -- si --> D[Extraemos el payload en variables]
    D --> E[Ejecutamos la logica principal]

Cuando escribes guard case, codificas literalmente este esquema: “si no es el caso correcto, no hay nada mas que hacer”.

4. Ejemplo: utilidades alrededor de Command en CLI

Para que no parezca que if case y guard case son sintaxis “por la sintaxis”, vamos a integrarlo en un pequeno armazon CLI. Seguimos la idea de “el comando como dato”: Command describe lo que el usuario quiere hacer, y el codigo de procesamiento del comando ejecuta la accion.

Supongamos que tenemos una lista de libros (por ahora solo [String]), y los comandos:

  • .add(title: String)
  • .remove(index: Int)
  • .list
  • .exit

Hoy no vamos a escribir un “parser de cadenas” completo (eso es otra tarea), sino que nos centraremos en el procesamiento de comandos ya preparados y en las comprobaciones puntuales de casos.

Procesamos todos los comandos con switch

Primero mostramos el manejador principal. Aqui switch es logico, porque hay varias opciones y cada una tiene su propia logica.

import Foundation

enum Command { case add(title: String), remove(index: Int), list, exit }

func handle(_ command: Command, books: inout [String]) {
    switch command {
    case let .add(title): books.append(title)
    case let .remove(i):  if i >= 0 && i < books.count { books.remove(at: i) }
    case .list:           print(books)
    case .exit:           print("Exit")
    }
}

Este fragmento es el “centro de control de vuelos”.

Comprobacion puntual: “¿es exit?”

A veces no necesitamos procesar el comando, sino decidir si continuamos el bucle de lectura de comandos. Aqui if case se ve ideal: una comprobacion, una accion.

import Foundation

enum Command { case list, exit }

func shouldStop(_ command: Command) -> Bool {
    if case .exit = command { return true }
    return false
}

print(shouldStop(.list)) // false
print(shouldStop(.exit)) // true

Esto se lee como una “condicion de parada” sin ceremonia innecesaria.

guard case para validar “el comando debe ser remove”

Imagina que tenemos una funcion “intentar borrar un libro”, y solo tiene sentido para .remove. Entonces guard case permite describir la intencion con la maxima honestidad: “si no es remove, no hacemos absolutamente nada”.

import Foundation

enum Command { case remove(index: Int), list }

func tryRemove(_ command: Command, from books: inout [String]) {
    guard case let .remove(i) = command else { return }
    guard i >= 0 && i < books.count else { return }
    books.remove(at: i)
}

Aqui dos guard seguidos se ven logicos: el primero “sobre la forma del comando”, el segundo “sobre la correccion de los datos”. Y este es el estilo clasico de Swift: salidas tempranas en lugar de if anidados.

5. Errores tipicos

Error n.° 1: confundir el patron y el valor (if case command = .exit).
En if case y guard case, a la izquierda siempre va el patron (por ejemplo, .exit o .add(title: ...)), y a la derecha el valor que se comprueba. Esto resulta poco habitual despues de las comparaciones “normales”, por eso el error aparece con frecuencia. La solucion es simple: dilo en voz alta como “el patron encaja con el valor”.

Error n.° 2: olvidar let al extraer datos y sorprenderse de que la variable no exista.
Si escribes if case .add(title) = command, el compilador no entendera de donde salio title: eso no es una declaracion de variable. Se necesita la forma if case let .add(title) = command. Lo mismo vale para guard case let ...

Error n.° 3: usar guard case pero no finalizar el alcance en else.
Por las reglas de Swift, guard debe “salir” del contexto actual: return, break, continue. Intentar escribir guard case .exit = command else { print("not exit") } no compilaria. Y eso es bueno: el lenguaje te protege de situaciones en las que el codigo sigue ejecutandose “como si nada” aunque la comprobacion haya fallado.

Error n.° 4: construir una cadena larga de if case en lugar de un switch normal.
if case funciona bien como herramienta puntual. Pero si tienes tres o cuatro variantes y cada una tiene su propia logica, switch casi siempre sera mas claro. Y hay un bonus agradable: el compilador comprobara que no has olvidado procesar algun caso.

Error n.° 5: extraer payload pero no usarlo, ensuciando el codigo con “nombres innecesarios”.
Si no necesitas el valor, mejor no extraerlo, o sustituirlo por _. Esto hace que la intencion del codigo sea mas honesta: el lector ve que ignoras una parte de los datos de forma consciente, y no que “simplemente olvidaste usar la variable”.

1
Tarea
Swift SELF, nivel 26, lección 2
Bloqueada
Modo del terminal
Modo del terminal
1
Tarea
Swift SELF, nivel 26, lección 2
Bloqueada
Libro por comando
Libro por comando
1
Tarea
Swift SELF, nivel 26, lección 2
Bloqueada
Eliminación segura
Eliminación segura
1
Tarea
Swift SELF, nivel 26, lección 2
Bloqueada
Flujo de eventos
Flujo de eventos
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION