Saltar al contenido
K
Swift: el lenguajePaso a paso35 min Básico

Funciones y switch

Al terminar: Escribir funciones con etiquetas de argumento y valores por defecto, y usar switch exhaustivo.

Al terminar tendrás la regla de los puntos escrita con un switch sobre el marcador, puntos(para:contra:), y titular(golesLocal:golesVisitante:), que convierte un marcador en una frase con pattern matching. Por el camino ves lo que Swift añade a una función: las etiquetas de argumento.

Importa porque en Swift la etiqueta forma parte del nombre de la función, y porque el switch obliga a cubrir todos los casos: el compilador te avisa de cada hueco antes de ejecutar. Esas dos ideas aparecen en cada línea de Swift que leerás.

Temas: etiquetas de argumento; switch exhaustivo, pattern matching y where. Los valores por defecto y inout van en la lección siguiente.

El fichero de hoy

Hoy trabajas sobre todo en Puntos.swift, el fichero donde escribiste puntosPor en La regla de los puntos y tu primer test. Ábrelo ahora: en el Project navigator de Xcode, Sources → Torneo → Puntos.swift (o open -a Xcode ~/Marcador-Swift/Torneo/Sources/Torneo/Puntos.swift desde la Terminal). Verás puntosPor con su if. Al final de la lección tendrá dos funciones más, y las ampliarás de una en una.

Los ejemplos sueltos de la teoría y el ejercicio los escribes en tu Playground de Xcode, el que creaste en Tu primer paquete de Swift. Reutiliza el mismo y borra lo anterior antes de pegar otro ejemplo: dos funciones con el mismo nombre y las mismas etiquetas chocan.

Funciones: la etiqueta es parte del nombre

Cada parámetro tiene dos nombres: la etiqueta, que escribes al llamar, y el nombre, que se usa dentro del cuerpo.

Cómo se declaraCómo se llamaQué pasa
func f(goles: Int)f(goles: 2)Un solo nombre: etiqueta y nombre son el mismo
func f(para goles: Int)f(para: 2)Etiqueta para, nombre interno goles
func f(_ goles: Int)f(2)_ quita la etiqueta

El nombre completo de una función incluye las etiquetas: puntosPor(golesAFavor:golesEnContra:). Por eso Swift permite tener dos funciones con el mismo nombre y etiquetas distintas.

Dos diferencias con Kotlin, donde pasabas argumentos con nombre en Funciones y parámetros. En Swift las etiquetas son obligatorias (no puedes escribir solo los valores, sin etiquetas, salvo que la función use _), y van en el orden declarado. Si declaras puntos(para:contra:) y la llamas puntos(contra: 1, para: 2), el compilador dice argument 'para' must precede argument 'contra'; y si la llamas puntos(2, 1), dice missing argument labels 'para:contra:' in call. En Kotlin, con nombres, el orden da igual.

switch: exhaustivo, sin caída y con patrones

switch es el when de Swift, con cuatro diferencias:

  • Exhaustivo siempre. Un when de Kotlin que solo ejecuta cosas, sin usar su valor, no exige else; un switch de Swift exige cubrir todos los casos, uses o no su valor. Con un número o un texto, eso pide un default.
  • Sin caída. Cada case termina solo y no sigue con el de abajo (en otros lenguajes hay que cortar a mano con break). Aquí no hace falta.
  • Patrones. Un case puede comparar un valor, un rango (0...45), una tupla, y ligar valores a nombres con let.
  • where añade una condición a un patrón.

Una tupla es un grupo de valores entre paréntesis, como (2, 1). Un switch sobre (golesLocal, golesVisitante) compara los dos valores a la vez, y el comodín _ significa «cualquier valor, no me interesa». Una tupla también puede ser el tipo de un parámetro, como marcador: (Int, Int), y se lee por posición: marcador.0 es el primer valor y marcador.1, el segundo.

ejemplo mínimo: etiquetas, una tupla y un switch (con comentarios)
// `_ a`: sin etiqueta al llamar. `con b`: etiqueta con, nombre interno b.
func sumar(_ a: Int, con b: Int) -> Int { a + b }
 
// Una tupla como parámetro: `marcador.0` y `marcador.1` son sus dos valores.
func golesTotales(_ marcador: (Int, Int)) -> Int { marcador.0 + marcador.1 }
 
func tramo(minuto: Int) -> String {
    switch minuto {                       // compara minuto con cada case, en orden
    case 0...45: "primer tiempo"          // un rango: de 0 a 45, los dos incluidos
    case 46...90: "segundo tiempo"        // sin break: ningún case cae al siguiente
    default: "prórroga"                   // obligatorio: un Int tiene infinitos valores
    }
}
 
print(sumar(1, con: 2))      // 3
print(tramo(minuto: 47))     // segundo tiempo
print(golesTotales((2, 1)))  // 3

Lo que imprime:

3
segundo tiempo
3

Aquí el switch es el cuerpo de la función y cada case es una sola expresión sin return: el valor del case elegido es el resultado. Escribir return en cada case también vale, y es la forma que verás en puntos(para:contra:). tramo reparte el minuto en tres casos, como el when de puntosPor que escribiste en Condiciones: if y when.

Dos piezas más del switch, con otros datos: si la temperatura sube, se mantiene o baja. case let (h, a) liga cada valor de la tupla a un nombre nuevo (aquí h y a), y where añade una condición que ese case debe cumplir además de encajar.

ejemplo mínimo: switch sobre una tupla con let y where
// Etiquetas hoy: y ayer:; dentro se usan gradosHoy y gradosAyer.
func tendencia(hoy gradosHoy: Int, ayer gradosAyer: Int) -> String {
    switch (gradosHoy, gradosAyer) {             // la tupla junta los dos valores
    case let (h, a) where h > a:                 // let liga cada valor a un nombre; where pone la condición
        return "sube"
    case let (h, a) where h == a:                // solo se mira si el case anterior no encajó
        return "igual"
    default:                                     // el compilador no razona los where: hace falta
        return "baja"
    }
}
 
print(tendencia(hoy: 20, ayer: 18))   // sube
print(tendencia(hoy: 10, ayer: 12))   // baja

case (let h, let a) where … es la misma forma con el let dentro de cada parte.

Kotlin frente a Swift

Kotlin (tu código de Kotlin)Swift
fun puntosPor(…): Int = when { … }func puntosPor(…) -> Int { … }
when { a > b -> 3; a == b -> 1; else -> 0 }switch (a, b) { case let (x, y) where x > y: … default: … }
else -> …default: …
Argumentos con nombre, en cualquier ordenEtiquetas obligatorias, en el orden declarado
in 0..45 -> …case 0...45: …
when { … } sin sujetoNo existe: se usa switch sobre una tupla, como aquí, o if / else if

Reto · Ejercicio: el arranque del partido

En tu Playground, escribe una función, descripcion, que reciba el minuto y los goles de los dos equipos juntos en una tupla, y elija la respuesta con un solo switch que mire a la vez el minuto y el total de goles. Debe devolver «Nada que contar» si el minuto está entre 0 y 9 y no hay goles, «Arranque con goles» si el minuto está entre 0 y 9 y los hay, y «Va el partido» en cualquier otro caso. Imprime la descripción del minuto 3 con 0-0, la del minuto 3 con 1-0 y la del minuto 60 con 2-1. Lo que debe salir:

Nada que contar
Arranque con goles
Va el partido
Ver solución
solución del ejercicio
func descripcion(minuto: Int, goles: (Int, Int)) -> String {
    switch (minuto, goles.0 + goles.1) {   // una tupla: el minuto y el total
    case (0...9, 0): "Nada que contar"     // minuto de 0 a 9 y ningún gol
    case (0...9, _): "Arranque con goles"  // minuto de 0 a 9 y cualquier total
    default: "Va el partido"
    }
}
print(descripcion(minuto: 3, goles: (0, 0)))
print(descripcion(minuto: 3, goles: (1, 0)))
print(descripcion(minuto: 60, goles: (2, 1)))

El orden de los case importa: el segundo solo se alcanza si el primero no encajó, y el default recoge el resto.

Paso · puntos(para:contra:) y titular

Objetivo: ampliar Puntos.swift con las etiquetas y el switch, y ampliar main.swift. Al terminar, xcrun swift run torneo-cli imprime siete titulares de marcadores.

Marcador-Swift/Torneo/
Sources/
Torneo/Puntos.swiftse modifica
torneo-cli/main.swiftse modifica
Dos ficheros que cambian.

Abre una Terminal y escribe cd ~/Marcador-Swift/Torneo. Puntos.swift ya existe y lo tienes abierto; main.swift está en Sources → torneo-cli. Vas en dos tramos, y compilas y ejecutas al terminar cada uno.

Tramo 1 · puntos(para:contra:): etiquetas y switch

Piezas nuevas:

  • func puntos(para golesAFavor: Int, contra golesEnContra: Int): cada parámetro con etiqueta y nombre distintos.
  • switch (a, b) { … } y case let (x, y) where x > y:: el let y el where del ejemplo tendencia de arriba, ahora con los goles.
  • return valor en cada case, o solo valor: las dos formas valen.

puntosPor(golesAFavor:golesEnContra:) se conserva, pero ahora delega en puntos(para:contra:), así que los tests de la lección anterior siguen pasando.

Intenta escribirlo tú: sustituye el contenido de Puntos.swift por dos funciones públicas.

  1. puntos, que recibe los goles a favor con la etiqueta para y los goles en contra con la etiqueta contra, y devuelve 3 si se gana, 1 si se empata y 0 si se pierde, mirando los dos valores a la vez.
  2. puntosPor, la de siempre (mismas etiquetas, golesAFavor y golesEnContra), que ahora se limita a llamar a puntos.
Intenta primero. Cuando termines, compara con la solución.Mostrar el código
Puntos.swift: tramo 1, el fichero entero
// Se conserva con la firma de siempre, pero ahora delega en `puntos(para:contra:)`.
public func puntosPor(golesAFavor: Int, golesEnContra: Int) -> Int {
    puntos(para: golesAFavor, contra: golesEnContra)
}
 
// Etiqueta y nombre distintos: se llama `puntos(para: 2, contra: 1)`
// y dentro se usan `golesAFavor` y `golesEnContra`.
public func puntos(para golesAFavor: Int, contra golesEnContra: Int) -> Int {
    // `switch` sobre una tupla: compara los dos valores a la vez.
    switch (golesAFavor, golesEnContra) {
    case let (aFavor, enContra) where aFavor > enContra:   // `let` liga los dos valores
        return 3
    case let (aFavor, enContra) where aFavor == enContra:  // `where` añade una condición
        return 1
    default:           // obligatorio: el compilador no razona sobre los `where`
        return 0
    }
}

Ejecuta xcrun swift run torneo-cli: la salida es la de siempre, porque puntosPor da lo mismo que antes.

Ahora provoca el error del switch: borra el default de puntos(para:contra:) (la línea default: y su return 0) y ejecuta xcrun swift build. El compilador se detiene con error: switch must be exhaustive y la nota add missing case: '(_, _)': no razona sobre los where, así que no sabe que «mayor» e «igual» dejan solo «menor». Devuelve el default a su sitio.

Tramo 2 · titular: el switch con patrones

Los siete casos de titular(golesLocal:golesVisitante:), en este orden (se aplica el primer case que encaja) y con estos textos exactos (la lección siguiente te hace copiar unos tests que los comprueban):

CasoTexto que devuelve
Marcador (0, 0)Sin goles
Empate (mismos goles)Empate a N, con los goles de cada equipo en lugar de N
Gana el local sin recibir golesEl local gana sin recibir goles
Gana el visitante sin recibir golesEl visitante gana sin recibir goles
Diferencia de tres o más golesGoleada
Gana el localGana el local
Cualquier otroGana el visitante

Piezas nuevas:

  • case (0, 0): y case (_, 0):: comparar la tupla con valores concretos; _ es el comodín.
  • abs(local - visitante): valor absoluto, una función de la librería estándar.
  • for (local, visitante) in [(0, 0), (2, 2)] { … }: recorre una lista de tuplas y separa cada una en dos nombres. Las listas se ven a fondo en Colecciones y key paths.

Las tres piezas con otros datos: dónde cae un punto (x, y) en un plano.

ejemplo mínimo: valores concretos, comodín, abs y for sobre tuplas
func dondeEsta(x: Int, y: Int) -> String {
    switch (x, y) {
    case (0, 0):                                // los dos valores tienen que ser 0
        "En el origen"
    case (_, 0):                                // x, cualquiera (_); y, exactamente 0
        "Sobre el eje X"
    case (0, _):                                // x, 0; y, cualquiera
        "Sobre el eje Y"
    case let (x, y) where abs(x) == abs(y):     // abs quita el signo: abs(-3) es 3
        "En una diagonal"
    default:                                    // todo lo que no encajó arriba
        "En otro sitio"
    }
}
 
// Cada vuelta toma una tupla de la lista y la separa en x e y.
for (x, y) in [(0, 0), (4, 0), (-3, 3), (1, 5)] {
    print("(\(x), \(y)): \(dondeEsta(x: x, y: y))")
}
// (0, 0): En el origen · (4, 0): Sobre el eje X · (-3, 3): En una diagonal · (1, 5): En otro sitio

Intenta escribirlo tú, al final de Puntos.swift: titular, una función pública que recibe los goles con las etiquetas golesLocal y golesVisitante y devuelve el texto de la tabla de arriba, mirando los dos valores a la vez y en ese orden.

Intenta primero. Cuando termines, compara con la solución.Mostrar el código
Puntos.swift: tramo 2, titular, al final del fichero
// Cada `case` es una sola expresión sin `return`: su valor es el resultado.
// Se aplica el primer `case` que encaja, por eso el orden importa.
public func titular(golesLocal: Int, golesVisitante: Int) -> String {
    switch (golesLocal, golesVisitante) {
    case (0, 0):
        "Sin goles"
    case (let local, let visitante) where local == visitante:
        "Empate a \(local)"
    case (_, 0):                        // `_`: el local marcó lo que sea y el visitante, 0
        "El local gana sin recibir goles"
    case (0, _):
        "El visitante gana sin recibir goles"
    case let (local, visitante) where abs(local - visitante) >= 3:
        "Goleada"
    case let (local, visitante) where local > visitante:
        "Gana el local"
    default:
        "Gana el visitante"
    }
}

En main.swift, después del doble bucle de marcadores (el que imprime … puntos para el local) y antes del comentario // Una lista de tres jugadores, intenta escribir tú un fragmento que recorra estos siete marcadores, 0-0, 2-2, 3-0, 0-1, 5-1, 2-1 y 1-3, e imprima cada uno con su titular. Lo que debes ver:

0-0: Sin goles
2-2: Empate a 2
3-0: El local gana sin recibir goles
0-1: El visitante gana sin recibir goles
5-1: Goleada
2-1: Gana el local
1-3: Gana el visitante
Intenta primero. Cuando termines, compara con la solución.Mostrar el código
main.swift: fragmento, los titulares
// Una lista de tuplas: cada vuelta separa la tupla en `local` y `visitante`.
for (local, visitante) in [(0, 0), (2, 2), (3, 0), (0, 1), (5, 1), (2, 1), (1, 3)] {
    print("\(local)-\(visitante): \(titular(golesLocal: local, golesVisitante: visitante))")
}

Ejecútalo: deben salir siete líneas, de 0-0: Sin goles a 1-3: Gana el visitante.

Punto de control

  • Desde ~/Marcador-Swift/Torneo, xcrun swift build termina con Build complete!.
  • Desde ~/Marcador-Swift/Torneo, xcrun swift run torneo-cli imprime, además de lo de siempre, los siete titulares (de 0-0: Sin goles a 1-3: Gana el visitante, con 5-1: Goleada entre ellos), antes del bloque de opcionales.
  • Desde ~/Marcador-Swift/Torneo, xcrun swift test termina con Test run with 11 tests in 2 suites passed (ignora la línea Executed 0 tests de XCTest): los 4 de PuntosTests y los 7 de JugadorTests, que siguen pasando porque puntosPor da lo mismo.
  • Quitaste el default de puntos(para:contra:), leíste switch must be exhaustive y lo devolviste a su sitio.
  • Sabes decir por qué titular da Sin goles para 0-0 y no Empate a 0, y por qué 3-0 da El local gana sin recibir goles y no Goleada.

Comprueba lo que entendiste

5 preguntas · necesitas 4 para completar la lección

  1. 1.Una función se declara func puntos(para golesAFavor: Int, contra golesEnContra: Int) -> Int. ¿Cómo se llama?

  2. 2.Una función se declara func puntos(para golesAFavor: Int, contra golesEnContra: Int) -> Int y la llamas puntos(contra: 1, para: 2). ¿Qué ocurre?

  3. 3.Escribes un switch (a, b) sobre una tupla de dos Int, con un case … where para «a mayor que b», otro para «a igual a b» y sin default. ¿Qué pasa?

  4. 4.En titular(golesLocal: 3, golesVisitante: 0), ¿por qué devuelve «El local gana sin recibir goles» y no «Goleada»?

  5. 5.En case (_, 0): de un switch sobre una tupla de goles, ¿qué significa el _?

Faltan 5 de 5