Я считаю, что продолжения - это особый случай обратных вызовов. Функция может вызывать любое количество функций, любое количество раз. Например:
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, выполняют оптимизацию хвостовых вызовов. Это означает, что хвостовой вызов не влечет за собой полную нагрузку при вызове функции. Вместо этого он реализован в виде простого перехода (с заменой кадра стека вызывающей функции на кадр стека хвостового вызова).
Бонус : переход к продолжению прохождения стиля. Рассмотрим следующую программу:
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);
}
Этот стиль программирования, в котором вам не разрешено возвращать значения (и, следовательно, вы должны прибегать к передаче продолжения), называется стилем передачи продолжения.
Однако есть две проблемы со стилем прохождения продолжения:
- Передача продолжения увеличивает размер стека вызовов. Если вы не используете такой язык, как Scheme, который исключает хвостовые вызовы, вы рискуете исчерпать пространство стека.
- Больно писать вложенные функции.
Первая проблема может быть легко решена в JavaScript путем асинхронного вызова продолжений. Вызывая продолжение асинхронно, функция возвращается до вызова продолжения. Следовательно, размер стека вызовов не увеличивается:
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
продолжения, которая оборачивает остальную часть тела функции после вызова 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
может использоваться для реализации сложных структур управления потоками, таких как блоки try-catch, сопрограммы, генераторы, волокна и т. Д.