Tôi tin rằng sự tiếp tục là một trường hợp đặc biệt của các cuộc gọi lại. Một chức năng có thể gọi lại bất kỳ số lượng chức năng, bất kỳ số lần. Ví dụ:
var array = [1, 2, 3];
forEach(array, function (element, array, index) {
array[index] = 2 * element;
});
console.log(array);
function forEach(array, callback) {
var length = array.length;
for (var i = 0; i < length; i++)
callback(array[i], array, i);
}
Tuy nhiên, nếu một chức năng gọi lại một chức năng khác như là điều cuối cùng nó thực hiện thì chức năng thứ hai được gọi là tiếp tục của chức năng đầu tiên. Ví dụ:
var array = [1, 2, 3];
forEach(array, function (element, array, index) {
array[index] = 2 * element;
});
console.log(array);
function forEach(array, callback) {
var length = array.length;
// This is the last thing forEach does
// cont is a continuation of forEach
cont(0);
function cont(index) {
if (index < length) {
callback(array[index], array, index);
// This is the last thing cont does
// cont is a continuation of itself
cont(++index);
}
}
}
Nếu một chức năng gọi một chức năng khác là điều cuối cùng thì nó được gọi là một cuộc gọi đuôi. Một số ngôn ngữ như Scheme thực hiện tối ưu hóa cuộc gọi đuôi. Điều này có nghĩa là cuộc gọi đuôi không phát sinh toàn bộ chi phí của cuộc gọi chức năng. Thay vào đó, nó được triển khai như một goto đơn giản (với khung ngăn xếp của chức năng gọi được thay thế bằng khung ngăn xếp của cuộc gọi đuôi).
Tiền thưởng : Tiếp tục phong cách tiếp tục vượt qua. Hãy xem xét chương trình sau:
console.log(pythagoras(3, 4));
function pythagoras(x, y) {
return x * x + y * y;
}
Bây giờ nếu mọi thao tác (bao gồm cả phép nhân, phép nhân, v.v.) được viết dưới dạng hàm thì chúng ta sẽ có:
console.log(pythagoras(3, 4));
function pythagoras(x, y) {
return add(square(x), square(y));
}
function square(x) {
return multiply(x, x);
}
function multiply(x, y) {
return x * y;
}
function add(x, y) {
return x + y;
}
Ngoài ra, nếu chúng tôi không được phép trả lại bất kỳ giá trị nào thì chúng tôi sẽ phải sử dụng các phần tiếp theo như sau:
pythagoras(3, 4, console.log);
function pythagoras(x, y, cont) {
square(x, function (x_squared) {
square(y, function (y_squared) {
add(x_squared, y_squared, cont);
});
});
}
function square(x, cont) {
multiply(x, x, cont);
}
function multiply(x, y, cont) {
cont(x * y);
}
function add(x, y, cont) {
cont(x + y);
}
Kiểu lập trình này trong đó bạn không được phép trả về các giá trị (và do đó bạn phải dùng đến các phần tiếp theo đi qua) được gọi là kiểu chuyển tiếp.
Tuy nhiên, có hai vấn đề với phong cách tiếp tục:
- Vượt qua các phần tiếp theo làm tăng kích thước của ngăn xếp cuộc gọi. Trừ khi bạn đang sử dụng một ngôn ngữ như Scheme để loại bỏ các cuộc gọi đuôi, bạn sẽ có nguy cơ hết dung lượng ngăn xếp.
- Thật đau đớn khi viết các hàm lồng nhau.
Vấn đề đầu tiên có thể được giải quyết dễ dàng bằng JavaScript bằng cách gọi liên tục không đồng bộ. Bằng cách gọi tiếp tục không đồng bộ, hàm trả về trước khi tiếp tục được gọi. Do đó, kích thước ngăn xếp cuộc gọi không tăng:
Function.prototype.async = async;
pythagoras.async(3, 4, console.log);
function pythagoras(x, y, cont) {
square.async(x, function (x_squared) {
square.async(y, function (y_squared) {
add.async(x_squared, y_squared, cont);
});
});
}
function square(x, cont) {
multiply.async(x, x, cont);
}
function multiply(x, y, cont) {
cont.async(x * y);
}
function add(x, y, cont) {
cont.async(x + y);
}
function async() {
setTimeout.bind(null, this, 0).apply(null, arguments);
}
Vấn đề thứ hai thường được giải quyết bằng cách sử dụng một hàm gọi call-with-current-continuation
là thường được viết tắt là callcc
. Thật không may, callcc
không thể thực hiện đầy đủ trong JavaScript, nhưng chúng tôi có thể viết một hàm thay thế cho hầu hết các trường hợp sử dụng của nó:
pythagoras(3, 4, console.log);
function pythagoras(x, y, cont) {
var x_squared = callcc(square.bind(null, x));
var y_squared = callcc(square.bind(null, y));
add(x_squared, y_squared, cont);
}
function square(x, cont) {
multiply(x, x, cont);
}
function multiply(x, y, cont) {
cont(x * y);
}
function add(x, y, cont) {
cont(x + y);
}
function callcc(f) {
var cc = function (x) {
cc = x;
};
f(cc);
return cc;
}
Các callcc
chức năng có một chức năng f
và áp dụng nó vào current-continuation
(viết tắt là cc
). Đây current-continuation
là một hàm tiếp tục kết thúc phần còn lại của thân hàm sau khi gọi đến callcc
.
Hãy xem xét cơ thể của chức năng pythagoras
:
var x_squared = callcc(square.bind(null, x));
var y_squared = callcc(square.bind(null, y));
add(x_squared, y_squared, cont);
Thứ current-continuation
hai callcc
là:
function cc(y_squared) {
add(x_squared, y_squared, cont);
}
Tương tự current-continuation
đầu tiên callcc
là:
function cc(x_squared) {
var y_squared = callcc(square.bind(null, y));
add(x_squared, y_squared, cont);
}
Vì cái current-continuation
đầu tiên callcc
chứa cái khác, callcc
nó phải được chuyển đổi thành kiểu chuyển tiếp:
function cc(x_squared) {
square(y, function cc(y_squared) {
add(x_squared, y_squared, cont);
});
}
Vì vậy, về cơ bản callcc
chuyển đổi toàn bộ cơ thể chức năng trở lại những gì chúng ta đã bắt đầu (và đặt tên cho các chức năng ẩn danh đó cc
). Hàm pythagoras sử dụng triển khai callcc này sẽ trở thành:
function pythagoras(x, y, cont) {
callcc(function(cc) {
square(x, function (x_squared) {
square(y, function (y_squared) {
add(x_squared, y_squared, cont);
});
});
});
}
Một lần nữa, bạn không thể triển khai callcc
bằng JavaScript, nhưng bạn có thể triển khai nó theo kiểu chuyển tiếp trong JavaScript như sau:
Function.prototype.async = async;
pythagoras.async(3, 4, console.log);
function pythagoras(x, y, cont) {
callcc.async(square.bind(null, x), function cc(x_squared) {
callcc.async(square.bind(null, y), function cc(y_squared) {
add.async(x_squared, y_squared, cont);
});
});
}
function square(x, cont) {
multiply.async(x, x, cont);
}
function multiply(x, y, cont) {
cont.async(x * y);
}
function add(x, y, cont) {
cont.async(x + y);
}
function async() {
setTimeout.bind(null, this, 0).apply(null, arguments);
}
function callcc(f, cc) {
f.async(cc);
}
Hàm này callcc
có thể được sử dụng để thực hiện các cấu trúc dòng điều khiển phức tạp như khối bắt thử, coroutines, máy phát điện, sợi , v.v.