javascript - iterators - Cómo clonar el generador ES6?
javascript function*yield (3)
Estoy tratando de crear una mónada List
en ES6 usando generadores. Para que funcione, necesito crear una copia de un iterador que ya haya consumido varios estados. ¿Cómo puedo clonar un iterador en ES6?
function* test() {
yield 1;
yield 2;
yield 3;
}
var x = test();
console.log(x.next().value); // 1
var y = clone(x);
console.log(x.next().value); // 2
console.log(y.next().value); // 2 (sic)
He intentado clone
y cloneDeep
desde lodash
, pero no lodash
. Los iteradores que se devuelven de esta manera son funciones nativas y mantienen su estado internamente, por lo que parece que no hay forma de hacerlo con su propio código JS.
Los iteradores [...] mantienen su estado internamente, así que parece que no hay forma de hacerlo.
Sí, y eso por una buena razón. No puedes clonar el estado, o de lo contrario podrías alterar demasiado el generador.
Sin embargo, es posible crear un segundo iterador que se ejecute junto al primero, al memorizar su secuencia y cederla más tarde nuevamente. Sin embargo, debería haber solo un iterador que realmente maneje el generador; de lo contrario, ¿cuál de sus clones podría enviar los argumentos next()
?
No puedes clonar un generador, es solo una función sin estado. Lo que podría haber estado, y por lo tanto lo que podría ser clonado, es el iterador resultante de invocar la función del generador.
Este enfoque almacena en caché los resultados intermedios, de modo que los iteradores clonados puedan acceder a ellos si es necesario hasta que se "pongan al día". Devuelve un objeto que es a la vez un iterador y un iterable, por lo que puede llamar a next
en él o for...of
encima. Se puede pasar cualquier iterador, por lo que en teoría podría haber clonado iteradores sobre una matriz al pasar en array.values()
. Cualquiera que sea el clon que llame primero el primero en un punto dado en la iteración tendrá el argumento pasado al next
, si lo hay, reflejado en el valor del yield
en el generador subyacente.
function clonableIterator(it) {
var vals = [];
return function make(n) {
return {
next(arg) {
const len = vals.length;
if (n >= len) vals[len] = it.next(arg);
return vals[n++];
},
clone() { return make(n); },
throw(e) { if (it.throw) it.throw(e); },
return(v) { if (it.return) it.return(v); },
[Symbol.iterator]() { return this; }
};
}(0);
}
function *gen() {
yield 1;
yield 2;
yield 3;
}
var it = clonableIterator(gen());
console.log(it.next());
var clone = it.clone();
console.log(clone.next());
console.log(it.next());
Obviamente, este enfoque tiene el problema de que mantiene el historial completo del iterador. Una optimización sería mantener un WeakMap
de todos los iteradores clonados y lo lejos que han progresado, y luego limpiar el historial para eliminar todos los valores pasados que ya han sido consumidos por todos los clones.