Toda vez que uma função é chamada, um novo frame de pilha é criado na memória para guardar os argumentos e as variáveis locais da função. Esse frame de pilha é desalocado quando a função retorna. Como Gleam usa chamadas de função recursivas em vez de uma sintaxe de laço, esses frames de pilha podem acabar consumindo uma grande quantidade de memória.
Para evitar esse problema, Gleam oferece suporte a otimização de chamada de cauda, que permite ao compilador reutilizar o frame de pilha da função atual quando uma chamada de função é a última coisa que a função faz. Isso significa que uma função pode chamar a si mesma infinitas vezes sem usar memória adicional.
Muitas vezes, dá para reescrever funções recursivas não otimizadas como funções com otimização de chamada de cauda usando um acumulador.
Um acumulador é uma variável passada junto com os dados. Ele serve para carregar o estado atual da execução da função até que o caso base seja alcançado.
Quem cria o acumulador deve ser quem escreveu a função, e não quem a usa. Para isso, declare duas funções: uma função pública que recebe apenas os dados necessários como argumentos e inicializa o acumulador, e uma função privada que também recebe um 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
}
}
}