Kevin ชี้ให้เห็นอย่างชัดเจนว่าข้อมูลโค้ดนี้ทำงานอย่างไร (รวมถึงสาเหตุที่เข้าใจไม่ได้) แต่ฉันต้องการเพิ่มข้อมูลบางอย่างเกี่ยวกับวิธีการใช้แทรมโพลีนในงานทั่วไป
หากไม่มีการเพิ่มประสิทธิภาพ tail-call (TCO) การเรียกใช้ฟังก์ชันทุกครั้งจะเพิ่มเฟรมสแต็กไปยังสแต็กการเรียกใช้ปัจจุบัน สมมติว่าเรามีฟังก์ชั่นในการพิมพ์ตัวเลขนับถอยหลัง:
function countdown(n) {
if (n === 0) {
console.log("Blastoff!");
} else {
console.log("Launch in " + n);
countdown(n - 1);
}
}
ถ้าเราโทรcountdown(3)
มาลองวิเคราะห์ว่า call stack จะมีลักษณะอย่างไรโดยไม่มี 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
ด้วย TCO การเรียกแบบเรียกซ้ำแต่ละครั้งจะcountdown
อยู่ในตำแหน่งท้าย (ไม่มีอะไรเหลือให้ทำนอกจากส่งคืนผลลัพธ์ของการโทร) ดังนั้นจึงไม่มีการจัดสรรเฟรมสแต็ก โดยไม่ต้อง TCO n
กองพัดขึ้นแม้มีขนาดใหญ่ขึ้นเล็กน้อย
Trampolining รับข้อ จำกัด นี้โดยการใส่เสื้อคลุมรอบcountdown
ฟังก์ชั่น จากนั้นcountdown
จะไม่ทำการโทรซ้ำและส่งคืนฟังก์ชันที่จะเรียกแทนทันที นี่คือตัวอย่างการใช้งาน:
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);
}
}
เพื่อให้เข้าใจวิธีการทำงานได้ดีขึ้นลองดูที่ call stack:
> 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
ในแต่ละขั้นตอนcountdownHop
ฟังก์ชั่นจะยกเลิกการควบคุมโดยตรงว่าจะเกิดอะไรขึ้นต่อไปแทนที่จะส่งคืนฟังก์ชันที่จะเรียกที่อธิบายถึงสิ่งที่มันต้องการจะเกิดขึ้นต่อไป ฟังก์ชั่นแทรมโพลีนจะรับสิ่งนี้และเรียกมันจากนั้นเรียกฟังก์ชั่นอะไรก็ตามที่ส่งคืนและต่อไปเรื่อย ๆ จนกว่าจะไม่มี "ขั้นตอนต่อไป" สิ่งนี้เรียกว่า trampolining เนื่องจากการไหลของการควบคุม "ตีกลับ" ระหว่างการเรียกซ้ำและการใช้แทรมโพลีนแทนการทำซ้ำการทำงานโดยตรง โดยละทิ้งการควบคุมมากกว่าการที่จะทำให้การเรียก recursive ฟังก์ชัน trampoline สามารถมั่นใจได้สแต็คไม่ได้มีขนาดใหญ่เกินไป หมายเหตุด้านข้าง: การนำไปใช้ของtrampoline
ละเว้นการส่งคืนค่าเพื่อความเรียบง่าย
มันอาจเป็นเรื่องยุ่งยากที่จะรู้ว่านี่เป็นความคิดที่ดีหรือไม่ ประสิทธิภาพอาจประสบเนื่องจากแต่ละขั้นตอนจัดสรรการปิดใหม่ การเพิ่มประสิทธิภาพที่ชาญฉลาดสามารถทำให้สิ่งนี้เป็นจริงได้ แต่คุณไม่มีทางรู้ การส่งแทรมโพลีนเป็นประโยชน์สำหรับการ จำกัด การเรียกซ้ำอย่างหนักเช่นเมื่อการนำภาษาไปใช้กำหนดขนาดสแต็กการโทรสูงสุด
loopy
ไม่ล้นเพราะมันไม่ได้เรียกตัวเองว่า