Valahányszor meghívunk egy függvényt, a memóriában létrejön egy új veremkeret, amely a függvény argumentumait és helyi változóit tárolja, és ez a veremkeret felszabadul, amikor a függvény visszatér. Mivel a Gleam a ciklusok helyett rekurzív függvényhívásokat használ, ezek a veremkeretek könnyen rengeteg memóriát foglalhatnak le.
Ennek a problémának az elkerülésére a Gleam támogatja a farokhívás-optimalizálást, aminek köszönhetően a fordító újrahasznosíthatja az aktuális függvény veremkeretét, ha a függvény utolsó művelete egy függvényhívás. Ez azt jelenti, hogy egy függvény végtelen sokszor meghívhatja önmagát anélkül, hogy további memóriát használna.
A nem optimalizált rekurzív függvények gyakran átírhatók farokhívás-optimalizált függvényekké egy akkumulátor segítségével.
Az akkumulátor egy olyan változó, amelyet az adatok mellett adunk át. Arra szolgál, hogy átadja a függvény végrehajtásának aktuális állapotát, amíg el nem érjük az alapesetet.
Az akkumulátor kezdőértékét a függvény írójának kell megadnia, nem a függvény használójának. Ehhez két függvényt kell deklarálni: egy nyilvános függvényt, amely csak a szükséges adatokat kapja argumentumként, és megadja az akkumulátor kezdőértékét, valamint egy privát függvényt, amely már az akkumulátort is megkapja.
// 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
}
}
}