जब भी किसी फंक्शन को कॉल किया जाता है, तो मेमोरी में एक नया स्टैक फ्रेम बनता है, जिसमें उस फंक्शन के आर्गुमेंट और लोकल वेरिएबल रखे जाते हैं। फंक्शन के लौटते ही यह स्टैक फ्रेम मुक्त कर दिया जाता है। 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
}
}
}