ฉันเชื่อว่าความต่อเนื่องเป็นกรณีพิเศษของการโทรกลับ ฟังก์ชันอาจเรียกกลับจำนวนฟังก์ชันจำนวนเท่าใดก็ได้ ตัวอย่างเช่น:
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);
}
อย่างไรก็ตามหากฟังก์ชันเรียกฟังก์ชันอื่นกลับมาเป็นสิ่งสุดท้ายที่ทำฟังก์ชันที่สองจะเรียกว่าต่อเนื่องจากฟังก์ชันแรก ตัวอย่างเช่น:
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);
}
}
}
หากฟังก์ชันเรียกใช้ฟังก์ชันอื่นเป็นสิ่งสุดท้ายจะเรียกว่าการเรียกหาง บางภาษาเช่น Scheme ทำการเพิ่มประสิทธิภาพการโทรหาง ซึ่งหมายความว่าการเรียกหางจะไม่เกิดค่าใช้จ่ายทั้งหมดของการเรียกฟังก์ชัน แทนที่จะใช้เป็น goto แบบธรรมดา (โดยสแต็กเฟรมของฟังก์ชันการโทรถูกแทนที่ด้วยสแต็กเฟรมของการโทรหาง)
โบนัส : เป็นไปตามรูปแบบการส่งต่ออย่างต่อเนื่อง พิจารณาโปรแกรมต่อไปนี้:
console.log(pythagoras(3, 4));
function pythagoras(x, y) {
return x * x + y * y;
}
ตอนนี้ถ้าทุกการดำเนินการ (รวมถึงการบวกการคูณ ฯลฯ ) ถูกเขียนในรูปแบบของฟังก์ชันเราจะมี:
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;
}
นอกจากนี้หากเราไม่ได้รับอนุญาตให้ส่งคืนค่าใด ๆ เราจะต้องใช้ความต่อเนื่องดังนี้:
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);
}
รูปแบบของการเขียนโปรแกรมที่คุณไม่ได้รับอนุญาตให้ส่งคืนค่า (ดังนั้นคุณต้องหันไปใช้การส่งผ่านความต่อเนื่องรอบ ๆ ) เรียกว่าสไตล์การส่งผ่านความต่อเนื่อง
อย่างไรก็ตามมีปัญหาสองประการเกี่ยวกับรูปแบบการส่งต่อแบบต่อเนื่อง:
- การส่งผ่านความต่อเนื่องจะเพิ่มขนาดของ call stack เว้นแต่คุณจะใช้ภาษาเช่น Scheme ซึ่งกำจัดการโทรหางคุณจะเสี่ยงต่อการใช้พื้นที่สแต็กเต็ม
- การเขียนฟังก์ชันซ้อนกันเป็นความเจ็บปวด
ปัญหาแรกสามารถแก้ไขได้อย่างง่ายดายใน JavaScript โดยการเรียกใช้ความต่อเนื่องแบบอะซิงโครนัส โดยการเรียกความต่อเนื่องแบบอะซิงโครนัสฟังก์ชันจะส่งกลับก่อนที่จะเรียกใช้ความต่อเนื่อง ดังนั้นขนาดของ call stack จึงไม่เพิ่มขึ้น:
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);
}
ปัญหาที่สองมักจะแก้ไขได้โดยใช้ฟังก์ชั่นที่เรียกว่าซึ่งมักจะถูกเรียกโดยย่อว่าcall-with-current-continuation
callcc
น่าเสียดายที่callcc
ไม่สามารถใช้งานได้อย่างสมบูรณ์ใน JavaScript แต่เราสามารถเขียนฟังก์ชันทดแทนสำหรับกรณีการใช้งานส่วนใหญ่ได้:
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;
}
callcc
ฟังก์ชั่นใช้เวลาฟังก์ชั่นf
และนำไปใช้กับcurrent-continuation
(ย่อเป็นcc
) current-continuation
เป็นฟังก์ชันต่อเนื่องซึ่ง wraps callcc
ขึ้นส่วนที่เหลือของร่างกายทำงานหลังจากที่การเรียกร้องให้
พิจารณาเนื้อหาของฟังก์ชั่นpythagoras
:
var x_squared = callcc(square.bind(null, x));
var y_squared = callcc(square.bind(null, y));
add(x_squared, y_squared, cont);
ประการcurrent-continuation
ที่สองcallcc
คือ:
function cc(y_squared) {
add(x_squared, y_squared, cont);
}
ในทำนองเดียวกันสิ่งcurrent-continuation
แรกcallcc
คือ:
function cc(x_squared) {
var y_squared = callcc(square.bind(null, y));
add(x_squared, y_squared, cont);
}
เนื่องจากส่วนcurrent-continuation
แรกcallcc
มีอีกรายการหนึ่งcallcc
จึงต้องถูกแปลงเป็นรูปแบบการส่งต่อความต่อเนื่อง:
function cc(x_squared) {
square(y, function cc(y_squared) {
add(x_squared, y_squared, cont);
});
}
ดังนั้นโดยพื้นฐานแล้วcallcc
เหตุผลที่จะแปลงเนื้อความของฟังก์ชันทั้งหมดกลับไปเป็นสิ่งที่เราเริ่มต้น (และตั้งชื่อฟังก์ชันที่ไม่ระบุตัวตนเหล่านั้นcc
) ฟังก์ชัน pythagoras โดยใช้ callcc นี้จะกลายเป็น:
function pythagoras(x, y, cont) {
callcc(function(cc) {
square(x, function (x_squared) {
square(y, function (y_squared) {
add(x_squared, y_squared, cont);
});
});
});
}
อีกครั้งคุณไม่สามารถใช้งานcallcc
ใน JavaScript ได้ แต่คุณสามารถใช้รูปแบบการส่งผ่านความต่อเนื่องใน JavaScript ได้ดังนี้:
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);
}
ฟังก์ชันcallcc
นี้สามารถใช้เพื่อปรับใช้โครงสร้างการไหลของการควบคุมที่ซับซ้อนเช่นบล็อกลองจับโครูทีนเครื่องกำเนิดไฟฟ้าเส้นใยฯลฯ