每次调用一个函数时,都会在内存中创建一个新的栈帧,用来存放该函数的实参和局部变量;函数返回时,这个栈帧就会被释放。由于 Gleam 使用递归函数调用而不是循环语法,这些栈帧可能会占用大量内存。
为了避免这个问题,Gleam 支持尾调用优化:如果一次函数调用是函数做的最后一件事,编译器就能复用当前函数的栈帧。这意味着函数可以调用自身无数次,而不占用任何额外的内存。
未经优化的递归函数往往可以通过使用_累加器_改写成经过尾调用优化的函数。
累加器是一个随数据一起传递的变量。它用来传递函数执行过程中的当前状态,直到抵达基本情况。
累加器应当由函数的作者初始化,而不是由函数的调用者初始化。为此,需要声明两个函数:一个公开函数,只接收必要的数据作为实参并初始化累加器;一个私有函数,额外接收一个累加器。
// 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
}
}
}