Je crois que les continuations sont un cas particulier de callbacks. Une fonction peut rappeler un nombre quelconque de fonctions, un nombre quelconque de fois. Par exemple :
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);
}
Cependant, si une fonction rappelle une autre fonction en dernier lieu, la deuxième fonction est appelée une continuation de la première. Par exemple :
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);
}
}
}
Si une fonction appelle une autre fonction en dernier lieu, cela s'appelle un appel de fin. Certains langages comme Scheme optimisent les appels de queue. Cela signifie que l'appel de queue ne subit pas toute la surcharge d'un appel de fonction. Au lieu de cela, il est implémenté comme un simple goto (avec le cadre de pile de la fonction appelante remplacé par le cadre de pile de l'appel de queue).
Bonus : Procéder à la continuation du style de passing. Considérons le programme suivant :
console.log(pythagoras(3, 4));
function pythagoras(x, y) {
return x * x + y * y;
}
Maintenant, si chaque opération (y compris l'addition, la multiplication, etc.) était écrite sous la forme de fonctions, nous aurions :
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;
}
En outre, si nous n'étions pas autorisés à renvoyer des valeurs, nous devrions utiliser des continuations comme suit :
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);
}
Ce style de programmation dans lequel vous n'êtes pas autorisé à renvoyer des valeurs (et où vous devez donc faire circuler des continuations) s'appelle le style continuation passing.
Il y a cependant deux problèmes avec le style de passe de continuation :
- Le passage de continuations augmente la taille de la pile d'appels. À moins que vous n'utilisiez un langage comme Scheme qui élimine les appels de queue, vous risquez de manquer d'espace sur la pile.
- Il est difficile d'écrire des fonctions imbriquées.
Le premier problème peut être facilement résolu en JavaScript en appelant les continuations de manière asynchrone. En appelant la continuation de manière asynchrone, la fonction revient avant que la continuation ne soit appelée. Ainsi, la taille de la pile d'appels n'augmente pas :
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);
}
Le second problème est généralement résolu à l'aide d'une fonction appelée call-with-current-continuation
qui est souvent abrégé en callcc
. Malheureusement callcc
ne peut pas être entièrement implémenté en JavaScript, mais nous pouvons écrire une fonction de remplacement pour la plupart de ses cas d'utilisation :
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;
}
Le site callcc
prend une fonction f
et l'applique à la current-continuation
(en abrégé cc
). Le site current-continuation
est une fonction de continuation qui reprend le reste du corps de la fonction après l'appel à la fonction callcc
.
Considérons le corps de la fonction pythagoras
:
var x_squared = callcc(square.bind(null, x));
var y_squared = callcc(square.bind(null, y));
add(x_squared, y_squared, cont);
Le site current-continuation
du second callcc
est :
function cc(y_squared) {
add(x_squared, y_squared, cont);
}
De même, le current-continuation
du premier callcc
est :
function cc(x_squared) {
var y_squared = callcc(square.bind(null, y));
add(x_squared, y_squared, cont);
}
Depuis le current-continuation
du premier callcc
contient un autre callcc
il doit être converti en style de passage de continuation :
function cc(x_squared) {
square(y, function cc(y_squared) {
add(x_squared, y_squared, cont);
});
}
Donc, essentiellement callcc
convertit logiquement le corps entier de la fonction en ce que nous avons commencé (et donne à ces fonctions anonymes le nom de cc
). La fonction pythagoras utilisant cette implémentation de callcc devient alors :
function pythagoras(x, y, cont) {
callcc(function(cc) {
square(x, function (x_squared) {
square(y, function (y_squared) {
add(x_squared, y_squared, cont);
});
});
});
}
Encore une fois, vous ne pouvez pas mettre en œuvre callcc
en JavaScript, mais vous pouvez l'implémenter à la manière du passage de continuation en JavaScript comme suit :
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);
}
La fonction callcc
peut être utilisé pour mettre en œuvre des structures de flux de contrôle complexes telles que des blocs try-catch, des coroutines, des générateurs, fibres etc.
8 votes
Une partie de moi pense que c'est une très bonne question et une autre partie pense qu'elle est trop longue et qu'elle aboutira probablement à une réponse par "oui ou non". Cependant, compte tenu de l'effort et des recherches nécessaires, je me fie à mon premier sentiment.
2 votes
Quelle est votre question ? On dirait que vous la comprenez très bien.
0 votes
Je voudrais demander : AJAX est-il une forme de "continuation" ?
0 votes
@MichaelAaronSafyan - Cela aurait probablement dû être un article de blog. J'étais juste confus après avoir lu toutes les explications sur les continuations sur le web. Je voulais donc juste savoir si ce que je comprends est vrai.
1 votes
AaditMShah me fait penser que cela devrait être sous forme de questions-réponses. Bien que je ne sache pas vraiment ce qu'est la "continuation".
3 votes
Oui, je suis d'accord - je pense qu'il aurait probablement dû être un article de blog plus dans le sens de " Continuations JavaScript - ce que je comprends d'elles ".
0 votes
@AlvinWong - AJAX est AJAX. Vous pouvez écrire une fonction qui fait un appel AJAX puis jette le résultat dans une continuation, dans ce cas on dit que la fonction est écrite dans le style continuation passing.
0 votes
@AndrasZoltan - J'ai enfin un sujet de blog. Maintenant, j'ai besoin d'un blog.
1 votes
Dois-je maintenant signaler cette question comme "Pas une vraie question" ?
11 votes
Eh bien, il y a une question essentielle : "Alors quelle est la différence entre une continuation et un callback ?", suivie d'un "je crois...". La réponse à cette question peut être intéressante ?
1 votes
@AaditMShah, je vous recommande de diviser cette question en une question 'différence entre un callback et une continuation' par exemple et de mettre le reste de la question dans une réponse, je pense que cela améliorerait cette question et permettrait aux autres de poster d'autres réponses possibles.
3 votes
Il semble qu'il serait plus approprié de poster ce message sur programmers.stackexchange.com.
2 votes
J'ai trouvé la définition de Wikipedia excellente : "En informatique et en programmation informatique, une continuation est une représentation abstraite de l'état de contrôle d'un programme informatique. Une continuation réifie l'état de contrôle du programme, c'est-à-dire que la continuation est une structure de données qui représente le processus de calcul à un point donné de l'exécution du processus ; la structure de données créée est accessible par le langage de programmation, au lieu d'être cachée dans l'environnement d'exécution". Le reste de l'article clarifie davantage le concept.
0 votes
@AaditMShah a ajouté quelques commentaires sur la terminologie dans votre réponse.
0 votes
Il existe un excellent poste sur le callCC en javascript avec le débogueur à pied dans votre navigateur. Vous pouvez avancer et voir comment le contrôle saute sur le code. Détails de
callCC
sont fournis dans la suite (post).0 votes
N'oubliez pas qu'il y a borné un sans limite les continuations. Habituellement, une continuation signifie qu'il n'y a pas de limite. Une continuation bornée est juste une fonction. Pour ce qui est de la continuation non bornée, d'après ce que j'ai compris, une fois qu'elle est appelée, aucun code en dehors d'elle ne compte plus, et le programme se termine après la fin de la continuation non bornée.