2014-10-03 8 views
12

Ich versuche, eine List Monade in ES6 mit Generatoren zu erstellen. Damit es funktioniert, muss ich eine Kopie eines Iterators erstellen, der bereits mehrere Zustände verbraucht hat. Wie klone ich einen Iterator in ES6?Wie man ES6 Generator klont?

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) 

habe ich clone und cloneDeep von lodash versucht, aber sie waren nicht von Nutzen. Iteratoren, die auf diese Weise zurückgegeben werden, sind native Funktionen und behalten ihren Status intern bei. Es scheint also, dass es keinen Weg gibt, dies mit eigenem JS-Code zu tun.

Antwort

5

Iteratoren [...] behalten ihren Zustand intern, so scheint es, gibt es keine Möglichkeit

Ja, und das aus gutem Grund. Sie können den Zustand nicht klonen, sonst könnten Sie den Generator zu sehr manipulieren.

Es könnte jedoch möglich sein, einen zweiten Iterator zu erstellen, der neben des ersten läuft, indem er seine Sequenz speichert und sie später wieder ergibt. Allerdings sollte es nur einen Iterator sein, die wirklich treibt den Generator - sonst, die Ihrer Klone erlaubt würde next() Argumente zu schicken?

+2

Memoizing die vorherigen Werte würden helfen, wenn ich vorherige Ergebnisse noch einmal bekommen will, aber das ist nicht der Punkt, der Frage. Ich hatte die Option, Argumente für "next" zu memotisieren, so dass ich einen weiteren Iterator vom selben Generator erstellen und ihn bis zum selben Punkt wiederholen konnte. Das Problem bei diesem Ansatz war, dass Funktionen in ES nicht rein sind, und es ist möglich, dass ich beim zweiten Durchlauf desselben Generators andere Ergebnisse erhalten würde. Ich denke, dass ich besser in einen Maillisten von "Harmonie" einsteigen und die Frage dort stellen sollte, wenn niemand mit einer besseren Idee des Klonens eines Iterators kommt. –

+1

Vielleicht verstehe ich Ihren Anwendungsfall nicht gut genug. Ist dein Generator eigentlich rein? Übergeben Sie jemals Argumente an '.next()'? Wie werden die beiden Iteratoren (ursprünglich und geklont) tatsächlich verbraucht? – Bergi

+0

Ich versuche, den folgenden Code auszuführen, der Haskell's nondeterminism Monade ähnelt (http://ideone.com/kGF9KY). Für jedes Element "x" des Arrays "iter.next (prev) .value" muss es als Argument an das nächste "request" übergeben werden. Auf diese Weise wird der Code nach "yield" mehrmals mit verschiedenen "Rückgabewerten" ausgeführt, daher kein Determinismus. –

3

Ich schrieb eine do-Notation Bibliothek für JavaScript, burrido. Um das Problem des veränderbaren Generators zu umgehen, habe ich immutagen gemacht, das einen unveränderlichen Generator emuliert, indem er eine Historie von Eingabewerten speichert und sie wiederholt, um den Generator in jedem bestimmten Zustand zu klonen.

+0

Süß! Ich arbeitete an der gleichen Sache und kam hierher: D –

3

Sie können nicht einen Generator klonen - es ist nur eine Funktion ohne Staat. Was einen Zustand haben könnte und daher geklont werden könnte, ist der Iterator, der sich aus dem Aufruf der Generatorfunktion ergibt.

Dieser Ansatz speichert Zwischenergebnisse, so dass geklonte Iteratoren auf sie zugreifen können, wenn nötig, bis sie „aufholen“. Sie gibt ein Objekt zurück, das sowohl ein Iterator als auch ein Iterator ist. Sie können also entweder next darauf oder for...of darüber aufrufen. Jeder Iterator kann übergeben werden, sodass Sie theoretisch Iteratoren über ein Array geklont haben könnten, indem Sie array.values() übergeben haben. Unabhängig davon, welcher Klon ruft next zuerst an einem gegebenen Punkt in der Iteration wird das Argument next weitergegeben, wenn überhaupt, in dem Wert der in dem darunterliegenden yield Generator reflektierte.

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());

Offensichtlich hat dieser Ansatz das Problem, dass es die gesamte Geschichte des Iterators hält. Eine Optimierung wäre, einen WeakMap aller geklonten Iteratoren und wie weit sie fortgeschritten sind, und dann den Verlauf aufzuräumen, um alle früheren Werte zu entfernen, die bereits von allen Klonen verbraucht wurden.

+0

Nette Implementierung, +1! Sie können auch "throw" und "return" Aufrufe weiterleiten. Wenn Sie nur an Iteratoren interessiert sind, sollten Sie nicht "arg" durchlaufen. – Bergi