প্রতিবার একটি ফাংশন কল করা হলে মেমরিতে নতুন একটি স্ট্যাক ফ্রেম তৈরি হয়, যেখানে ফাংশনের আর্গুমেন্ট ও লোকাল ভ্যারিয়েবল সংরক্ষণ করা হয়; আর ফাংশনটি রিটার্ন করলে এই স্ট্যাক ফ্রেমটি মেমরি থেকে মুক্ত করা হয়। 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
}
}
}