Devam etmenin özel bir geri arama durumu olduğuna inanıyorum. Bir işlev, herhangi bir sayıda işlevi herhangi bir sayıda geri çağırabilir. Örneğin:
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);
}
Bununla birlikte, bir işlev başka bir işlevi yaptığı son şey olarak geri çağırırsa, o zaman ikinci işlev, birincinin devamı olarak adlandırılır. Örneğin:
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);
}
}
}
Bir işlev, yaptığı son şey olarak başka bir işlevi çağırırsa, buna kuyruk çağrısı denir. Scheme gibi bazı diller, kuyruk arama optimizasyonları gerçekleştirir. Bu, kuyruk çağrısının bir işlev çağrısının tüm ek yüküne maruz kalmadığı anlamına gelir. Bunun yerine, basit bir goto olarak uygulanır (çağrı işlevinin yığın çerçevesi, kuyruk çağrısının yığın çerçevesi ile değiştirilir).
Bonus : Devam eden geçiş stiline geçme. Aşağıdaki programı düşünün:
console.log(pythagoras(3, 4));
function pythagoras(x, y) {
return x * x + y * y;
}
Şimdi, her işlem (toplama, çarpma vb. Dahil) işlevler biçiminde yazılsaydı, o zaman elde ederiz:
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;
}
Ek olarak, herhangi bir değer döndürmemize izin verilmezse, devam ettirmeleri aşağıdaki gibi kullanmak zorunda kalırdık:
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);
}
Değerleri döndürmenize izin verilmeyen bu programlama tarzına (ve bu nedenle devam ettirme yoluna başvurmanız gerekir), devamlılık geçiş stili denir.
Bununla birlikte, devam eden geçiş stiliyle ilgili iki sorun vardır:
- Devamlılıkların etrafından dolaşmak çağrı yığınının boyutunu artırır. Kuyruk aramalarını ortadan kaldıran Scheme gibi bir dil kullanmadığınız sürece yığın alanının tükenme riskini alırsınız.
- İç içe geçmiş işlevler yazmak acı veriyor.
İlk sorun, sürekliliği eşzamansız olarak çağırarak JavaScript'te kolayca çözülebilir. Sürekliliği eşzamansız olarak çağırarak işlev, devam çağrılmadan önce geri döner. Dolayısıyla çağrı yığını boyutu artmaz:
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);
}
İkinci problem genellikle call-with-current-continuation
olarak kısaltılan bir fonksiyon kullanılarak çözülür callcc
. Maalesef callcc
JavaScript'te tam olarak uygulanamaz, ancak kullanım durumlarının çoğu için bir değiştirme işlevi yazabiliriz:
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
Fonksiyon bir işlev alır f
ve uygular current-continuation
(olarak kısaltılmıştır cc
). current-continuation
Çağrısının işlev gövdesinin geri kalanı kadar kaydırılan bir devamıdır fonksiyonudur callcc
.
İşlevin gövdesini düşünün 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
İkinci callcc
bir:
function cc(y_squared) {
add(x_squared, y_squared, cont);
}
Benzer şekilde current-continuation
ilki callcc
:
function cc(x_squared) {
var y_squared = callcc(square.bind(null, y));
add(x_squared, y_squared, cont);
}
Yana current-continuation
ilk callcc
başka içeren callcc
bu devam geçen tarzı dönüştürülmesi gerekir:
function cc(x_squared) {
square(y, function cc(y_squared) {
add(x_squared, y_squared, cont);
});
}
Yani esasen callcc
mantıksal olarak tüm işlev gövdesini başladığımız şeye dönüştürür (ve bu anonim işlevlere adı verir cc
). Callcc'nin bu uygulamasını kullanan pisagor işlevi şu hale gelir:
function pythagoras(x, y, cont) {
callcc(function(cc) {
square(x, function (x_squared) {
square(y, function (y_squared) {
add(x_squared, y_squared, cont);
});
});
});
}
Yine callcc
JavaScript'te uygulayamazsınız , ancak bunu JavaScript'te devam geçiş stilini aşağıdaki gibi uygulayabilirsiniz:
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);
}
İşlev callcc
, dene-yakala blokları, eşduditler, üreteçler, lifler vb. Gibi karmaşık kontrol akış yapılarını uygulamak için kullanılabilir .