Cada vez que uma função é chamada, é criado na memória um novo registo de ativação para guardar os argumentos e as variáveis locais da função, e esse registo é libertado quando a função devolve. Como o Gleam usa chamadas de funções recursivas em vez de uma sintaxe de ciclos, isto pode fazer com que estes registos de ativação ocupem uma grande quantidade de memória.
Para evitar este problema, o Gleam suporta a otimização de chamadas em cauda, que permite ao compilador reutilizar o registo de ativação da função atual se uma chamada de função for a última coisa que a função faz. Isto significa que uma função se pode chamar a si própria um número infinito de vezes sem usar memória adicional.
As funções recursivas não otimizadas podem muitas vezes ser reescritas como funções otimizadas para chamadas em cauda com a ajuda de um acumulador.
Um acumulador é uma variável que é passada juntamente com os dados. Serve para transportar o estado atual da execução da função, até se chegar ao caso base.
Os acumuladores devem ser inicializados pelo autor da função, e não pelo utilizador da função. Para isso, declara 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
}
}
}