Kevin dengan singkat menunjukkan bagaimana potongan kode khusus ini bekerja (bersama dengan mengapa itu cukup tidak bisa dipahami), tetapi saya ingin menambahkan beberapa informasi tentang bagaimana trampolin pada umumnya bekerja.
Tanpa optimasi tail-call (TCO), setiap panggilan fungsi menambahkan bingkai tumpukan ke tumpukan eksekusi saat ini. Misalkan kita memiliki fungsi untuk mencetak hitungan mundur angka:
function countdown(n) {
if (n === 0) {
console.log("Blastoff!");
} else {
console.log("Launch in " + n);
countdown(n - 1);
}
}
Jika kita menelepon countdown(3)
, mari kita menganalisis bagaimana tumpukan panggilan akan terlihat tanpa TCO.
> countdown(3);
// stack: countdown(3)
Launch in 3
// stack: countdown(3), countdown(2)
Launch in 2
// stack: countdown(3), countdown(2), countdown(1)
Launch in 1
// stack: countdown(3), countdown(2), countdown(1), countdown(0)
Blastoff!
// returns, stack: countdown(3), countdown(2), countdown(1)
// returns, stack: countdown(3), countdown(2)
// returns, stack: countdown(3)
// returns, stack is empty
Dengan TCO, setiap panggilan rekursif countdown
berada di posisi ekor (tidak ada yang tersisa untuk dilakukan selain mengembalikan hasil panggilan) sehingga tidak ada bingkai tumpukan yang dialokasikan. Tanpa TCO, tumpukan meledak bahkan sedikit besar n
.
Trampolining mengatasi batasan ini dengan memasukkan pembungkus di sekitar countdown
fungsi. Kemudian, countdown
tidak melakukan panggilan rekursif dan sebaliknya segera mengembalikan fungsi untuk memanggil. Berikut ini contoh implementasi:
function trampoline(firstHop) {
nextHop = firstHop();
while (nextHop) {
nextHop = nextHop()
}
}
function countdown(n) {
trampoline(() => countdownHop(n));
}
function countdownHop(n) {
if (n === 0) {
console.log("Blastoff!");
} else {
console.log("Launch in " + n);
return () => countdownHop(n-1);
}
}
Untuk lebih memahami bagaimana ini bekerja, mari kita lihat tumpukan panggilan:
> countdown(3);
// stack: countdown(3)
// stack: countdown(3), trampoline
// stack: countdown(3), trampoline, countdownHop(3)
Launch in 3
// return next hop from countdownHop(3)
// stack: countdown(3), trampoline
// trampoline sees hop returned another hop function, calls it
// stack: countdown(3), trampoline, countdownHop(2)
Launch in 2
// stack: countdown(3), trampoline
// stack: countdown(3), trampoline, countdownHop(1)
Launch in 1
// stack: countdown(3), trampoline
// stack: countdown(3), trampoline, countdownHop(0)
Blastoff!
// stack: countdown(3), trampoline
// stack: countdown(3)
// stack is empty
Pada setiap langkah countdownHop
fungsi mengabaikan kontrol langsung dari apa yang terjadi selanjutnya, alih-alih mengembalikan fungsi untuk memanggil yang menggambarkan apa yang ingin terjadi selanjutnya. Fungsi trampolin kemudian mengambil ini dan menyebutnya, lalu memanggil fungsi apa pun yang kembali, dan seterusnya hingga tidak ada "langkah selanjutnya". Ini disebut trampolining karena aliran kontrol "memantul" antara setiap panggilan rekursif dan implementasi trampolin, alih-alih fungsi yang berulang secara langsung. Dengan mengabaikan kontrol siapa yang melakukan panggilan rekursif, fungsi trampolin dapat memastikan tumpukan tidak terlalu besar. Catatan: implementasi ini trampoline
menghilangkan nilai yang dikembalikan untuk kesederhanaan.
Sulit untuk mengetahui apakah ini ide yang bagus. Kinerja dapat menurun karena setiap langkah mengalokasikan penutupan baru. Optimalisasi yang cerdik dapat membuat ini layak, tetapi Anda tidak pernah tahu. Trampolining sebagian besar berguna untuk mengatasi batas rekursi keras, misalnya ketika implementasi bahasa menetapkan ukuran tumpukan panggilan maksimum.
loopy
tidak meluap karena tidak menyebut dirinya .