Ciclos y rangos
⏱ Dedicación recomendada: 0 minutos
Esto considera el contenido visible y relevante, e ignora texto colapsado o marcado como opcional.
r8vnhill/intro-kt
Repetir acciones es una necesidad común en cualquier programa: desde mostrar una lista de elementos hasta validar entradas o aplicar una función varias veces.Pero la forma en que lo hacemos importa.
En Kotlin, los ciclos están diseñados para ser expresivos y seguros. En esta lección exploraremos las principales formas de iteración: bucles for
, while
y do-while
, así como el uso de rangos y la función repeat
.
También aprenderemos a distinguir entre estructuras que pueden recorrerse y aquellas que solo pueden evaluarse, y veremos cómo Kotlin prioriza la legibilidad sobre una flexibilidad sin control.
🔁 Declaración for
En Kotlin,
for
es una declaración, no una expresión. Esto significa que no devuelve un valor: se usa para ejecutar instrucciones, como imprimir o modificar datos, no para producir resultados.
🧠 ¿Y en otros lenguajes?
A diferencia de Kotlin, donde for
es solo una declaración, en lenguajes como Scala, Haskell o Python existen formas de recorrer estructuras que también devuelven un valor.
📦 Scala (for
como expresión)
En Scala, los bucles for
pueden generar nuevas colecciones mediante yield
:
val doubled = for x <- List(1, 2, 3)
yield x * 2
// Resultado: List(2, 4, 6)
Esto se conoce como for-comprehension, y combina map
, flatMap
y filter
de forma declarativa.
🔢 Haskell (forM
monádico)
Aunque Haskell no tiene un bucle for
tradicional, puedes recorrer estructuras con efectos usando forM
del módulo Control.Monad
:
import Control.Monad (forM)
forM [1..3] (\x -> return (x * 2))
-- Resultado (en una mónada pura): [2, 4, 6]
forM
invierte los argumentos de mapM
para mayor claridad.
🐍 Python (comprensiones de listas)
Las list comprehensions permiten construir listas a partir de iteraciones:
[x * 2 for x in range(1, 4)]
# Resultado: [2, 4, 6]
También existen variantes para conjuntos y diccionarios.
En todos estos lenguajes, los bucles pueden devolver un valor al mismo tiempo que recorren estructuras.
En Kotlin, ese papel lo cumplen funciones como map
, filter
o fold
, mientras que for
está pensado para ejecutar acciones con efectos colaterales (como imprimir).
for (variable in rango) {
// cuerpo del ciclo
}
Las llaves {}
son opcionales si el cuerpo del ciclo tiene una sola línea, pero se recomienda mantenerlas por legibilidad y para evitar errores al modificar el código más adelante.
Ejemplos:
for (i in 1..5) print(i) // Imprime: 12345 (incluye el 5)
for (i in 5 downTo 1) print(i) // Imprime: 54321 (cuenta hacia atrás)
for (i in 1..5 step 2) print(i) // Imprime: 135 (salta de 2 en 2)
for (i in 1..<5) print(i) // Imprime: 1234 (excluye el 5)
for (i in 1 until 5) print(i) // Equivalente a 1..<5
1..5
: Crea un rango cerrado, que incluye el 5.5 downTo 1
: Cuenta en reversa.step
: Cambia el tamaño del paso (por ejemplo, saltar de 2 en 2)...<
yuntil
: Crean un rango semiabierto que excluye el límite superior.
También puedes usar rangos con otros tipos de datos, como caracteres:
for (c in 'a'..'e') print(c) // Imprime: abcde
for (c in 'e' downTo 'a') print(c) // Imprime: edcba
En Kotlin, los rangos no se limitan a enteros: puedes usarlos con cualquier tipo que implemente Comparable
, como Char
, String
, Double
, entre otros.
Esto permite escribir expresiones como:
'a'..'z' // Rango de caracteres
"Foo".."Foz" // Rango de strings por orden lexicográfico
1.0..5.0 // Rango de números en punto flotante
Ten en cuenta que no todos los rangos admiten iteración. Por ejemplo, 1.0..5.0
crea un rango válido, pero no puede usarse directamente en un bucle for
.
for
?No siempre.
Aunque muchos tipos pueden formar un rango, no todos los rangos pueden usarse en un ciclo for
.
Solo algunos tipos como Int
, Long
y Char
implementan lo necesario para que un rango sea también una secuencia iterable.
En cambio, rangos como 1.0..5.0
(Double
) o "Foo".."Foz"
(String
) no se pueden recorrer directamente con for
, porque Kotlin no sabe cómo avanzar de un valor al siguiente.
Piénsalo un momento:
- ¿Qué número sigue después de
1.0
?
¿1.1
,1.01
,1.000001
,1 + 1e-100
…? - ¿Y qué
String
viene después de"Foo"
?
¿"Fop"
?, ¿"Fooa"
?, ¿"Foofighters"
?
Estas preguntas no tienen una única respuesta correcta, por lo que Kotlin evita asumir una lógica arbitraria.
Por eso es importante distinguir entre crear un rango y recorrer un rango.
No todos los rangos son secuencias.
💡 ¿Y si quiero recorrer algo distinto?
Cualquier estructura que implemente Iterable puede recorrerse con un bucle for
.
Esto incluye listas, conjuntos, mapas (usando entries
), y muchas otras colecciones estándar.
val sins = listOf("Lust", "Gluttony", "Envy", "Greed", "Wrath", "Sloth", "Pride")
for (sin in sins) {
println("Homunculus: $sin")
}
Este fragmento imprime los nombres de los siete homúnculos que representan los pecados capitales en Fullmetal Alchemist.
Este comportamiento es posible gracias al patrón de iterador (Iterator
), que veremos más adelante en el curso.
📏 Verificar si un valor pertenece a un rango
En Kotlin, puedes usar el operador in
(y su negación !in
) para verificar si un valor está dentro de un rango.
Este operador devuelve un valor Boolean
, por lo que se puede usar directamente en condiciones:
if (x in 1..10) {
println("$x está entre 1 y 10")
}
if (x !in 20..30) {
println("$x no está entre 20 y 30")
}
También puedes usarlo dentro de expresiones when
:
when (x) {
in 1..10 -> println("Está entre 1 y 10")
!in 100..200 -> println("Está fuera del rango 100 a 200")
else -> println("Caso no contemplado")
}
El operador in
también funciona con otros tipos comparables como caracteres o strings:
if (c in 'a'..'z') {
println("$c es una letra minúscula")
}
if (s in "Foo".."Foz") {
println("$s está entre Foo y Foz")
}
🔍 ¿Cómo funciona realmente el operador in
?
Los operadores in
y !in
funcionan no solo con rangos, sino también con muchas estructuras estándar de Kotlin como listas, conjuntos y mapas.
val songs = listOf("Perfect Day", "Walk on the Wild Side", "Satellite of Love")
if ("Perfect Day" in songs) {
println("🎵 Lou Reed is in the playlist.")
}
Esto es posible porque estas colecciones implementan la función contains
.
Es decir, in
es simplemente una forma más legible de escribir songs.contains("Perfect Day")
.
in
con mis propias clases?Cualquier clase que defina la función contains
de forma adecuada puede usarse con in
y !in
.
En otras palabras, el operador in
es una forma alternativa de escribir una llamada a contains
, y puedes habilitarlo en tus propias clases si defines esa función.
Veremos cómo hacerlo más adelante en el curso, al estudiar sobrecarga de operadores.
🔄 Declaración while
En Kotlin, la declaración while
permite repetir un bloque de código mientras se cumpla una condición booleana.
Existen dos variantes principales:
while (condición) {
// cuerpo del ciclo
}
- Evalúa la condición antes de ejecutar el cuerpo.
- Si la condición es falsa desde el inicio, el cuerpo no se ejecuta.
do {
// cuerpo del ciclo
} while (condición)
- Ejecuta el cuerpo al menos una vez, y luego evalúa la condición.
- Es útil cuando necesitas que la acción se ejecute al menos una vez, como al pedir una entrada de usuario o realizar una verificación inicial.
Ambos estilos permiten repetir tareas de forma controlada:
- Usa
while
cuando la condición debe cumplirse desde el inicio. - Usa
do-while
cuando el cuerpo debe ejecutarse al menos una vez antes de volver a verificar la condición.
🌀 Ejemplo: Buscando configuración disponible
val sources = listOf("user.yaml", "project.yaml", "default.yaml")
var index = 0
var config: Config? = null
do {
val file = sources[index]
println("Intentando cargar configuración desde $file")
config = loadConfig(file)
index++
} while (config == null && index < sources.size)
if (config == null) {
error("No se pudo cargar ninguna configuración válida.")
}
En este ejemplo, una biblioteca de configuración intenta cargar archivos en un orden predefinido.
Se usa do-while
porque necesitamos intentar al menos una vez antes de saber si debemos continuar.
🔂 Función repeat
La función repeat
es una forma concisa de ejecutar un bloque de código un número específico de veces. Su sintaxis es:
repeat(n) {
// cuerpo del ciclo
}
repeat
es una función (no una declaración), lo que la convierte en una expresión.- Esto significa que puede usarse dentro de otras expresiones o asignarse a una variable.
- Sin embargo, devuelve
Unit
, por lo que su valor de retorno raramente se utiliza en la práctica:
repeat(3) {
println("Ni!")
}
Este ejemplo imprimirá "Ni!"
tres veces.
repeat
es ideal cuando necesitas repetir algo un número fijo de veces y no necesitas un índice.- Si sí necesitas saber en qué repetición estás (como en
for (i in 0 until n)
), puedes usar la versión con índice:
repeat(3) { i ->
println("Iteration $i")
}
🎯 Conclusiones
Los ciclos son una herramienta fundamental para controlar el flujo en cualquier lenguaje de programación, y en Kotlin se presentan de forma concisa, segura y expresiva. En esta lección exploramos las distintas formas de repetir acciones, desde ciclos for
sobre rangos hasta bucles controlados por condiciones y repeticiones fijas.
También aprendimos a distinguir entre lo que se puede crear (como un rango) y lo que se puede recorrer (como una secuencia iterable), una diferencia clave para evitar errores sutiles al diseñar código reutilizable.
🔑 Puntos clave
for
en Kotlin es una declaración, no una expresión: se usa para ejecutar acciones, no para producir valores.- Puedes recorrer rangos con
for
siempre que estos sean iterables, como los deInt
,Long
oChar
. - Otros tipos como
Double
oString
pueden formar rangos, pero no siempre pueden recorrerse. - El operador
in
permite verificar si un valor pertenece a un rango o a una colección. - Kotlin incluye dos variantes de ciclo condicional:
while
(evalúa antes) ydo-while
(evalúa después). - La función
repeat(n)
es una alternativa concisa para ejecutar un bloque un número fijo de veces.
🧰 ¿Qué nos llevamos?
Esta lección fue mucho más que un repaso de bucles: exploramos las distintas formas en que Kotlin nos permite expresar repetición con claridad y propósito.
Aprendimos a usar for
, while
, do-while
, repeat
, y a distinguir entre rangos recorribles y aquellos que no lo son. Pero sobre todo, entendimos que escribir ciclos no se trata de iterar por iterar, sino de elegir la herramienta que mejor comunica la intención del código.
Esta forma de pensar —priorizar expresividad, claridad y diseño— será fundamental al construir bibliotecas.Porque no basta con que algo funcione: debe ser legible, predecible y reusable.
Y si lo pensamos bien, aprender también es un ciclo: volvemos una y otra vez sobre los mismos conceptos, pero con más contexto, más herramientas, más claridad. Al igual que los ciclos en el código, lo importante no es solo repetir, sino repetir con intención.
Y todo eso —la expresividad, la intención y el diseño— también comienza en algo tan básico como los ciclos.
📖 Referencias
🔥 Recomendadas
- 🌐 Ranges and progressions de la documentación oficial de Kotlin: Explica cómo crear y recorrer rangos con diferentes tipos y pasos personalizados, lo que resulta esencial para comprender el uso de
for
,in
,step
, ydownTo
en Kotlin.
🔹 Adicionales
- 🎞 Ciclos en Kotlin: ¡Domina while, do-while y for! (Kotlin Bits #3) de Developer GB: Una introducción práctica al uso de
while
,do-while
yfor
en Kotlin con ejemplos sencillos como calcular factoriales o promedios; útil para personas que están comenzando a programar y quieren entender cómo y cuándo usar cada tipo de ciclo. - 📚 "Kotlin basics" (pp. 17–43) en "Kotlin in action" de Dmitry Jemerov & Svetlana Isakova: Presenta las estructuras de control como
if
,when
,for
y rangos como1..5
, destacando cómo Kotlin las hace más expresivas y uniformes que en Java, lo cual es clave para escribir ciclos claros y seguros.