Cada vez que se llama a una función se crea un nuevo marco de pila en memoria para guardar los argumentos y las variables locales de la función, y ese marco de pila se libera cuando la función devuelve. Como Gleam usa llamadas recursivas a funciones en lugar de una sintaxis de bucles, esto puede hacer que estos marcos de pila consuman una gran cantidad de memoria.
Para evitar este problema, Gleam admite la optimización de llamadas de cola, que permite al compilador reutilizar el marco de pila de la función actual si una llamada a función es lo último que hace la función. Esto significa que una función puede llamarse a sí misma un número infinito de veces sin usar memoria adicional.
A menudo, las funciones recursivas sin optimizar se pueden reescribir como funciones con optimización de llamadas de cola usando un acumulador.
Un acumulador es una variable que se pasa junto con los datos. Se usa para pasar el estado actual de la ejecución de la función hasta que se alcanza el caso base.
Los acumuladores los debe inicializar quien escribe la función, no quien la usa. Para lograrlo, declara dos funciones: una función pública que recibe solo los datos necesarios como argumentos y que inicializa el acumulador, y una función privada que también recibe un acumulador.
// Count the length of a list without tail call optimisation
pub fn count(list: List(String)) -> Int {
case list {
[] -> 0
[_, ..rest] -> {
let amount = count(rest) // Non-tail recursive call
amount + 1
}
}
}
// Count the length of a list with tail call optimisation
pub fn count(list: List(String)) -> Int {
count_elements(list, 0)
}
fn count_elements(list: List(String), accumulator: Int) -> Int {
case list {
[] -> accumulator
[_, ..rest] -> {
let accumulator = accumulator + 1
count_elements(rest, accumulator) // Tail recursive call
}
}
}