trubidata
Lección 4 de 14·18 min

Estructuras de control

En esta guía (13)

En esta lección vas a ver cómo controlar el flujo de un programa: tomar decisiones con if, repetir acciones con for y while, y elegir entre varias opciones con match. También vas a leer datos que escribe el usuario por teclado. Con todo eso, al final vas a programar un cajero automático por consola.

Una idea atraviesa toda la lección: en Scala, casi todo es una expresión que devuelve un valor, incluso if y match. Eso cambia la forma de escribir código respecto de Java o C.

Expresiones condicionales

Para tomar decisiones se usan if, else if y else:

scala
val edad = 25

if (edad < 18) {
  println("Sos menor de edad.")
} else if (edad >= 18 && edad < 65) {
  println("Sos un adulto.")
} else {
  println("Sos un adulto mayor.")
}

Como if es una expresión, podés guardar su resultado directamente en un val. Es el equivalente al operador ternario ? : de Java, que en Scala no existe porque no hace falta:

scala
val edad = 25
val categoria = if (edad < 18) "menor" else if (edad < 65) "adulto" else "mayor"
println(categoria) // adulto

Si un if no tiene else, su tipo es Unit: solo tiene sentido para efectos, como imprimir. Cuando quieras un valor, poné siempre el else.

Terminación de las sentencias

En Scala no hace falta el punto y coma (;) al final de cada línea: el salto de línea alcanza como separador. Solo lo necesitás si querés escribir dos sentencias en la misma línea, cosa que conviene evitar.

scala
val x = 10
val y = 20
val a = 15; val b = 25 // válido, pero menos legible

Bloques como expresiones

Como viste en la lección 2, un bloque {} vale lo que vale su última expresión. Eso permite calcular un valor en varios pasos sin dejar variables sueltas:

scala
val resultado = {
  val a = 5
  val b = 10
  a + b
}
println(resultado) // 15

Entrada y salida

Para mostrar datos ya conocés println; print hace lo mismo pero sin saltar de línea. Para leer lo que escribe el usuario se usan los métodos de scala.io.StdIn:

scala
import scala.io.StdIn

print("Ingresá tu nombre: ")
val nombre = StdIn.readLine()
print("Ingresá tu edad: ")
val edad = StdIn.readInt()
println(s"Hola, $nombre. Tenés $edad años.")
  • readLine() lee una línea como String.
  • readInt() y readDouble() leen una línea y la convierten a número. Si el usuario escribe algo que no es un número, lanzan una excepción (vas a ver cómo manejarlo en la lección 5).

Recordá que Scastie no permite leer del teclado: estos ejemplos los tenés que ejecutar en tu máquina.

Bucles

for

El bucle for recorre los elementos de un rango o una colección:

scala
for (i <- 1 to 5) {
  println(s"Iteración $i")
}

i <- 1 to 5 se lee "para cada i en el rango de 1 a 5". Algunas variantes de rango:

scala
for (i <- 1 to 5) print(s"$i ")        // 1 2 3 4 5   (incluye el 5)
println()
for (i <- 1 until 5) print(s"$i ")     // 1 2 3 4     (excluye el 5)
println()
for (i <- 10 to 0 by -2) print(s"$i ") // 10 8 6 4 2 0
println()

while

El bucle while repite mientras se cumpla una condición. Necesita una var que cambie en cada vuelta:

scala
var contador = 1
while (contador <= 5) {
  println(s"Contador: $contador")
  contador += 1
}

do-while

En Scala 2 existía también do-while, que ejecuta el cuerpo al menos una vez antes de evaluar la condición:

text
// Solo Scala 2: en Scala 3 no compila
var x = 5
do {
  println(s"x es igual a $x")
  x -= 1
} while (x > 0)

Scala 3 eliminó do-while. Si lo encontrás en código viejo, sabé leerlo. Para obtener el mismo comportamiento en Scala 3, poné el cuerpo y la condición dentro del bloque de un while común, con la condición como última expresión:

scala
var x = 5
while ({
  println(s"x es igual a $x")
  x -= 1
  x > 0
}) ()

El bloque se ejecuta siempre al menos una vez, y su último valor (x > 0) decide si se repite. El () final es el cuerpo vacío del while. Funciona, pero se lee peor: en la práctica, casi siempre conviene reescribirlo como un while normal.

Tampoco hay break ni continue como palabras del lenguaje. En Scala se prefiere expresar la condición de corte en el propio while, o usar las operaciones de colecciones que vas a ver en el módulo 2.

for avanzado y for con yield

El for de Scala es más poderoso que el de Java. Podés combinar varios generadores y agregar filtros (llamados guardas):

scala
for {
  i <- 1 to 3
  j <- 1 to 3
  if i != j
} println(s"($i, $j)")
// (1, 2) (1, 3) (2, 1) (2, 3) (3, 1) (3, 2), uno por línea

Y con yield, en lugar de ejecutar algo en cada vuelta, el for construye una colección nueva con los resultados. A esto se le llama for comprehension:

scala
val numeros = 1 to 5
val cuadrados = for (num <- numeros) yield num * num
println(cuadrados) // Vector(1, 4, 9, 16, 25)

val pares = for (n <- 1 to 10 if n % 2 == 0) yield n
println(pares) // Vector(2, 4, 6, 8, 10)

El for con yield es una expresión: no modifica nada, devuelve un valor. Lo vas a volver a encontrar en las lecciones 6 y 7.

match

match elige qué hacer según el valor de una expresión. Es la versión de Scala del switch de Java, pero mucho más poderosa:

scala
val opcion = 2

val descripcion = opcion match {
  case 1 => "Depositar"
  case 2 => "Retirar"
  case 3 => "Transferir"
  case 4 => "Salir"
  case _ => "Opción no válida"
}
println(descripcion) // Retirar
  • Cada case es un patrón. Se evalúan en orden y se ejecuta el primero que coincide.
  • _ es el comodín: coincide con cualquier valor. Si ningún case coincide y no hay comodín, se lanza un MatchError.
  • No hace falta break: nunca se "cae" al caso siguiente.
  • Como if, match es una expresión y devuelve un valor.

Podés agrupar alternativas con | y agregar condiciones con if (guardas):

scala
def clasificarMonto(monto: Double): String = monto match {
  case 0 => "sin movimiento"
  case m if m < 0 => "débito"
  case m if m >= 100000 => "crédito grande"
  case _ => "crédito"
}

def esFinDeSemana(dia: String): Boolean = dia match {
  case "sábado" | "domingo" => true
  case _ => false
}

println(clasificarMonto(-500))      // débito
println(clasificarMonto(250000))    // crédito grande
println(esFinDeSemana("domingo"))   // true

En case m if m < 0, m toma el valor que se está evaluando y la guarda decide si el caso aplica. match puede hacer mucho más que esto (reconocer tipos, desarmar estructuras); lo vas a ir viendo en las lecciones siguientes.

Practicá

  1. Validación de edad para abrir una cuenta. Escribí un programa que pida la edad del cliente y, con if/else if/else, muestre:
    • menor de 18: "Sos menor de edad. No podés abrir una cuenta."
    • de 18 a 64 inclusive: "Sos un adulto. Podés abrir una cuenta."
    • 65 o más: "Sos un adulto mayor. Podés abrir una cuenta especial."
  2. Interés compuesto año por año. Pedí el capital, la tasa de interés anual (en porcentaje) y la cantidad de años. Con un bucle for, mostrá el monto acumulado al final de cada año, usando la fórmula Monto = Capital * (1 + Tasa / 100) ^ Año.
  3. Clientes de alto valor neto. Pedí el saldo de un cliente y, con un bloque, asigná a un val clienteAltoValorNeto el valor true si el saldo es mayor o igual a 1.000.000, o false si no. Después mostrá un mensaje según el resultado.
  4. Cajero automático. Escribí un cajero que muestre un menú con cuatro opciones: depositar, retirar, consultar saldo y salir. Usá un while para mantener al usuario en el cajero hasta que elija salir, y un match para resolver cada opción. No se puede retirar más que el saldo.

Soluciones

Cada ejercicio está escrito como una función que después se llama: así podés tener los cuatro en el mismo archivo y ejecutar solo el que quieras.

scala
import scala.io.StdIn

// Ejercicio 1
def validarEdadParaCuenta(): Unit = {
  println("Ingresá tu edad:")
  val edad = StdIn.readInt()

  if (edad < 18) {
    println("Sos menor de edad. No podés abrir una cuenta.")
  } else if (edad <= 64) {
    println("Sos un adulto. Podés abrir una cuenta.")
  } else {
    println("Sos un adulto mayor. Podés abrir una cuenta especial.")
  }
}

// Ejercicio 2
def calcularInteresCompuesto(): Unit = {
  println("Ingresá el capital:")
  val principal = StdIn.readDouble()
  println("Ingresá la tasa de interés anual (%):")
  val tasaDeInteres = StdIn.readDouble()
  println("Ingresá la cantidad de años:")
  val anios = StdIn.readInt()

  var montoFinal = principal
  for (anio <- 1 to anios) {
    montoFinal *= (1 + tasaDeInteres / 100)
    println(f"Año $anio - Monto acumulado: $$$montoFinal%.2f")
  }
}

// Ejercicio 3
def identificarClienteAltoValorNeto(): Unit = {
  println("Ingresá el saldo de la cuenta:")
  val saldo = StdIn.readDouble()

  val clienteAltoValorNeto = {
    val umbral = 1000000.0
    saldo >= umbral
  }

  if (clienteAltoValorNeto) println("Es un cliente de alto valor neto.")
  else println("No es un cliente de alto valor neto.")
}

validarEdadParaCuenta()
calcularInteresCompuesto()
identificarClienteAltoValorNeto()
text
Ingresá tu edad:
30
Sos un adulto. Podés abrir una cuenta.
Ingresá el capital:
1000
Ingresá la tasa de interés anual (%):
10
Ingresá la cantidad de años:
3
Año 1 - Monto acumulado: $1100.00
Año 2 - Monto acumulado: $1210.00
Año 3 - Monto acumulado: $1331.00
Ingresá el saldo de la cuenta:
2500000
Es un cliente de alto valor neto.

Algunos detalles:

  • En el ejercicio 1, el else if (edad <= 64) no necesita volver a preguntar edad >= 18: si llegaste ahí, el primer if ya descartó a los menores.
  • En el ejercicio 2, en lugar de llamar a math.pow en cada vuelta, el monto se va multiplicando por (1 + tasa / 100): el resultado es el mismo. En el f"...", $$ muestra un signo $ literal y $montoFinal%.2f muestra el monto con dos decimales.
  • En el ejercicio 3, if (saldo >= 1000000) true else false funcionaría, pero es redundante: la comparación saldo >= umbral ya es un Boolean.
scala
// Ejercicio 4
import scala.io.StdIn

def cajeroAutomatico(): Unit = {
  var saldo = 1000.0 // saldo inicial
  var continuar = true

  while (continuar) {
    println("\nSeleccioná una opción:")
    println("1. Consultar saldo")
    println("2. Depositar")
    println("3. Retirar")
    println("4. Salir")

    val opcion = StdIn.readInt()

    opcion match {
      case 1 =>
        println(s"Saldo actual: $saldo")
      case 2 =>
        println("Ingresá el monto a depositar:")
        val deposito = StdIn.readDouble()
        if (deposito > 0) {
          saldo += deposito
          println(s"Depósito exitoso. Saldo actual: $saldo")
        } else {
          println("El monto tiene que ser mayor que cero.")
        }
      case 3 =>
        println("Ingresá el monto a retirar:")
        val retiro = StdIn.readDouble()
        if (retiro <= 0) {
          println("El monto tiene que ser mayor que cero.")
        } else if (retiro <= saldo) {
          saldo -= retiro
          println(s"Retiro exitoso. Saldo actual: $saldo")
        } else {
          println("Fondos insuficientes.")
        }
      case 4 =>
        println("Gracias por usar el cajero automático.")
        continuar = false
      case _ =>
        println("Opción no válida. Elegí una opción del 1 al 4.")
    }
  }
}

cajeroAutomatico()

El while depende de la variable continuar, que solo pasa a false cuando el usuario elige la opción 4. Fijate que también se validan los montos negativos o cero: sin eso, "retirar -500" sumaría saldo. Como ejercicio extra, agregá una opción de transferencia a una segunda cuenta.

Resumen

  • if y match son expresiones: devuelven un valor que podés guardar en un val.
  • El punto y coma es opcional; el salto de línea separa sentencias.
  • StdIn.readLine, readInt y readDouble leen datos del teclado; print y println los muestran.
  • for (i <- 1 to n) recorre rangos y colecciones; while repite mientras se cumpla una condición. do-while existía en Scala 2 pero se eliminó en Scala 3.
  • for con guardas y yield construye colecciones nuevas.
  • match compara contra patrones en orden, admite alternativas con |, guardas con if y el comodín _.

← Volver al temario