Κάθε φορά που καλείται μια συνάρτηση, δημιουργείται στη μνήμη ένα νέο πλαίσιο στοίβας για να αποθηκεύσει τα ορίσματα και τις τοπικές μεταβλητές της συνάρτησης, και αυτό το πλαίσιο στοίβας αποδεσμεύεται όταν η συνάρτηση επιστρέψει. Επειδή η 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
}
}
}