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:
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:
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.
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:
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:
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 comoString.readInt()yreadDouble()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:
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:
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:
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:
// 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:
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):
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:
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:
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
casees 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úncasecoincide y no hay comodín, se lanza unMatchError.- No hace falta
break: nunca se "cae" al caso siguiente. - Como
if,matches una expresión y devuelve un valor.
Podés agrupar alternativas con | y agregar condiciones con if (guardas):
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á
- 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."
- 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órmulaMonto = Capital * (1 + Tasa / 100) ^ Año. - Clientes de alto valor neto. Pedí el saldo de un cliente y, con un bloque, asigná a un
val clienteAltoValorNetoel valortruesi el saldo es mayor o igual a 1.000.000, ofalsesi no. Después mostrá un mensaje según el resultado. - Cajero automático. Escribí un cajero que muestre un menú con cuatro opciones: depositar, retirar, consultar saldo y salir. Usá un
whilepara mantener al usuario en el cajero hasta que elija salir, y unmatchpara 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.
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()
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 preguntaredad >= 18: si llegaste ahí, el primerifya descartó a los menores. - En el ejercicio 2, en lugar de llamar a
math.powen cada vuelta, el monto se va multiplicando por(1 + tasa / 100): el resultado es el mismo. En elf"...",$$muestra un signo$literal y$montoFinal%.2fmuestra el monto con dos decimales. - En el ejercicio 3,
if (saldo >= 1000000) true else falsefuncionaría, pero es redundante: la comparaciónsaldo >= umbralya es unBoolean.
// 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
ifymatchson expresiones: devuelven un valor que podés guardar en unval.- El punto y coma es opcional; el salto de línea separa sentencias.
StdIn.readLine,readIntyreadDoubleleen datos del teclado;printyprintlnlos muestran.for (i <- 1 to n)recorre rangos y colecciones;whilerepite mientras se cumpla una condición.do-whileexistía en Scala 2 pero se eliminó en Scala 3.forcon guardas yyieldconstruye colecciones nuevas.matchcompara contra patrones en orden, admite alternativas con|, guardas conify el comodín_.