Patrones de acumuladores
Mantenga el estado durante la recursión
Patrones de acumuladores es una lección gratuita de Scala for Backend Engineering & Functional Programming en CoddyKit. Esta es la lección 2 de 4. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de Scala for Backend Engineering & Functional Programming, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de Scala for Backend Engineering & Functional Programming incluye 4 lecciones en total.
Por qué usar acumuladores
La recursión simple construye el resultado al volver por la pila de llamadas, después de que la llamada recursiva termina.
En cambio, un acumulador lleva un resultado parcial a cada llamada, de modo que la respuesta está lista cuando se alcanza el caso base.
Este pequeño cambio permite usar recursión de cola y mantener constante el uso de la pila.
La función auxiliar
El patrón del acumulador utiliza una función auxiliar interna que recibe un parámetro adicional: el resultado acumulado hasta ese momento.
La función externa solo la inicia con un valor inicial, normalmente 0 o una lista vacía.
def sum(xs: List[Int]): Int = {
def loop(rest: List[Int], acc: Int): Int = rest match {
case Nil => acc
case h :: t => loop(t, acc + h)
}
loop(xs, 0)
}Ejecutar el acumulador
Aquí, el programa completo suma una lista mediante un acumulador.
Observe que el caso base devuelve acc directamente, no 0. El total se ha ido construyendo mientras recorríamos la lista.
def sum(xs: List[Int]): Int = {
def loop(rest: List[Int], acc: Int): Int = rest match {
case Nil => acc
case h :: t => loop(t, acc + h)
}
loop(xs, 0)
}
@main def run(): Unit =
println(sum(List(1, 2, 3, 4))) // 10Comparar las dos estructuras
En la recursión simple, el paso de combinación (h + ...) espera a que termine la llamada interna.
En la versión con acumulador, la combinación se realiza antes de la llamada, y la llamada es lo último que hace la función.
Esta propiedad de que sea la última llamada es lo que la convierte en recursión de cola.
// Plain: combine after the call
case h :: t => h + sum(t)
// Accumulator: combine before the call
case h :: t => loop(t, acc + h)Recursión de cola
Una llamada recursiva de cola es aquella en la que la llamada recursiva es la última acción de la función y no queda nada por hacer después.
Scala puede optimizarla convirtiéndola en un bucle que reutiliza un único marco de pila, por lo que nunca se desborda, independientemente de la profundidad.
import scala.annotation.tailrec
@tailrec
def countDown(n: Int): Unit =
if (n < 0) ()
else { println(n); countDown(n - 1) }La anotación @tailrec
Añadir @tailrec solicita al compilador que verifique que la función es realmente recursiva de cola.
Si no lo es, la compilación falla con un error claro. Así, una trampa de rendimiento silenciosa se convierte en una garantía comprobada durante la compilación.
import scala.annotation.tailrec
def sum(xs: List[Int]): Int = {
@tailrec
def loop(rest: List[Int], acc: Int): Int = rest match {
case Nil => acc
case h :: t => loop(t, acc + h)
}
loop(xs, 0)
}
@main def run(): Unit = println(sum((1 to 100000).toList))Acumular una lista
Los acumuladores no tienen por qué contener números. También pueden construir colecciones.
Esta función reverse antepone cada cabeza al acumulador, lo que invierte el orden de forma natural. Anteponer con :: es rápido, por lo que resulta eficiente.
def reverse[A](xs: List[A]): List[A] = {
def loop(rest: List[A], acc: List[A]): List[A] = rest match {
case Nil => acc
case h :: t => loop(t, h :: acc)
}
loop(xs, Nil)
}reverse en acción
El acumulador comienza vacío y crece a medida que consumimos la entrada.
Como cada cabeza se coloca al principio de acc, el primer elemento acaba siendo el último, lo que produce una lista invertida con un coste de pila constante.
def reverse[A](xs: List[A]): List[A] = {
def loop(rest: List[A], acc: List[A]): List[A] = rest match {
case Nil => acc
case h :: t => loop(t, h :: acc)
}
loop(xs, Nil)
}
@main def run(): Unit =
println(reverse(List(1, 2, 3))) // List(3, 2, 1)Varios acumuladores
Una función auxiliar puede llevar varios acumuladores a la vez.
Aquí seguimos un producto acumulado y un recuento en el mismo bucle, y devolvemos ambos como una tupla.
Cada uno pasa su valor actualizado a la siguiente llamada.
def stats(xs: List[Int]): (Int, Int) = {
def loop(rest: List[Int], prod: Int, count: Int): (Int, Int) =
rest match {
case Nil => (prod, count)
case h :: t => loop(t, prod * h, count + 1)
}
loop(xs, 1, 0)
}Elegir el valor inicial
El acumulador inicial debe ser el elemento neutro de la operación.
Para la suma, use 0; para la multiplicación, 1; para construir listas, Nil; y para concatenar cadenas, la cadena vacía.
Una semilla incorrecta produce respuestas erróneas sin que sea evidente.
// addition -> seed 0
// product -> seed 1
// list -> seed Nil
// string -> seed ""Orden de los resultados
La recursión con acumulador procesa los elementos de izquierda a derecha, pero un acumulador basado en anteponer los invierte.
Si necesita conservar el orden al construir una lista, inviértala al final o use append, aunque añadir al final es más lento. Anteponer y luego invertir es el modismo habitual.
def mapInc(xs: List[Int]): List[Int] = {
def loop(rest: List[Int], acc: List[Int]): List[Int] = rest match {
case Nil => acc.reverse
case h :: t => loop(t, (h + 1) :: acc)
}
loop(xs, Nil)
}Comprobación rápida
Elija la afirmación correcta sobre la recursión con acumulador.
Recapitulación
Un acumulador pasa el resultado parcial a través de las llamadas recursivas, de modo que el caso base puede devolverlo directamente.
Esto coloca la llamada recursiva en posición de cola, lo que permite la optimización de llamadas de cola de Scala y la comprobación de seguridad de @tailrec.
Inicialice el acumulador con el elemento neutro de la operación e invierta el resultado al final cuando el orden sea importante.
Preguntas frecuentes
¿La lección «Patrones de acumuladores» es gratis?
Sí — el texto completo de «Patrones de acumuladores» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de Scala for Backend Engineering & Functional Programming, actualiza a CoddyKit PRO. El curso de Scala for Backend Engineering & Functional Programming incluye 4 lecciones en total.
¿Qué aprenderé en «Patrones de acumuladores»?
Mantenga el estado durante la recursión Practicas Scala for Backend Engineering & Functional Programming con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.
¿Necesito experiencia previa para empezar Scala for Backend Engineering & Functional Programming?
No se requiere experiencia previa. Scala for Backend Engineering & Functional Programming en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 2 de 4.
¿Cuánto tiempo toma la lección «Patrones de acumuladores»?
La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.
¿Puedo escribir y ejecutar código en esta lección de Scala for Backend Engineering & Functional Programming?
Sí. Cada lección de Scala for Backend Engineering & Functional Programming incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.
Todas las lecciones de este curso
- Pensamiento recursivo
- Patrones de acumuladores
- foldLeft y foldRight
- reduce y aggregate