나는 연속이 특별한 콜백 사례라고 생각합니다. 함수는 여러 함수를 여러 번 콜백 할 수 있습니다. 예를 들면 다음과 같습니다.
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
. 이 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 블록, 코 루틴, 발전기, 섬유 등과 같은 복잡한 제어 흐름 구조를 구현하는 데 사용할 수 있습니다 .