Kevin ngắn gọn chỉ ra cách đoạn mã đặc biệt này hoạt động (cùng với lý do tại sao nó khá khó hiểu), nhưng tôi muốn thêm một số thông tin về trampolines cách nói chung công việc.
Không có tối ưu hóa cuộc gọi đuôi (TCO), mỗi lệnh gọi hàm sẽ thêm khung ngăn xếp vào ngăn xếp thực thi hiện tại. Giả sử chúng ta có chức năng in ra đếm ngược các số:
function countdown(n) {
if (n === 0) {
console.log("Blastoff!");
} else {
console.log("Launch in " + n);
countdown(n - 1);
}
}
Nếu chúng ta gọi countdown(3)
, hãy phân tích xem ngăn xếp cuộc gọi sẽ trông như thế nào nếu không có 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
Với TCO, mỗi lệnh gọi đệ quy countdown
ở vị trí đuôi (không còn gì để làm ngoài việc trả về kết quả của cuộc gọi) để không có khung ngăn xếp nào được phân bổ. Không có TCO, ngăn xếp thổi lên thậm chí hơi lớn n
.
Trampolining vượt qua giới hạn này bằng cách chèn một trình bao bọc xung quanh countdown
chức năng. Sau đó, countdown
không thực hiện các cuộc gọi đệ quy và thay vào đó ngay lập tức trả về một hàm để gọi. Đây là một ví dụ thực hiện:
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);
}
}
Để hiểu rõ hơn về cách thức hoạt động của tính năng này, hãy xem ngăn xếp cuộc gọi:
> 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
Tại mỗi bước countdownHop
chức năng từ bỏ quyền kiểm soát trực tiếp của những gì xảy ra tiếp theo, thay vì trở về một hàm để gọi đó là mô tả những gì nó sẽ giống như để xảy ra tiếp theo. Hàm trampoline sau đó lấy cái này và gọi nó, sau đó gọi bất kỳ hàm nào trả về, và cứ thế cho đến khi không có "bước tiếp theo". Điều này được gọi là trampolining vì luồng điều khiển "nảy" giữa mỗi lệnh gọi đệ quy và triển khai trampoline, thay vì chức năng trực tiếp lặp lại. Bằng cách từ bỏ quyền kiểm soát ai thực hiện cuộc gọi đệ quy, chức năng của tấm bạt lò xo có thể đảm bảo ngăn xếp không quá lớn. Lưu ý bên lề: việc thực hiện này trampoline
bỏ qua các giá trị trả về cho đơn giản.
Nó có thể là khó khăn để biết liệu đây là một ý tưởng tốt. Hiệu suất có thể bị ảnh hưởng do mỗi bước phân bổ một đóng cửa mới. Tối ưu hóa thông minh có thể làm cho điều này khả thi, nhưng bạn không bao giờ biết. Trampolining chủ yếu hữu ích để vượt qua các giới hạn đệ quy cứng, ví dụ khi triển khai ngôn ngữ đặt kích thước ngăn xếp cuộc gọi tối đa.
loopy
không tràn vì nó không tự gọi .