هر بار که یک تابع فراخوانی میشود، قاب پشتهای تازه در حافظه ساخته میشود تا آرگومانها و متغیرهای محلی تابع را نگه دارد و این قاب پشته هنگام بازگشت تابع آزاد میشود. چون 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
}
}
}