【问题标题】:How to clone ES6 generator?如何克隆 ES6 生成器? [复制]
【发布时间】:2014-11-28 13:20:42
【问题描述】:

我正在尝试使用生成器在 ES6 中创建 List monad。为了让它工作,我需要创建一个已经消耗了几个状态的迭代器的副本。如何在 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)

我从lodash 尝试过clonecloneDeep,但它们没有用。以这种方式返回的迭代器是原生函数,并在内部保持其状态,所以似乎没有办法使用自己的 JS 代码。

【问题讨论】:

    标签: javascript ecmascript-6 clone generator


    【解决方案1】:

    迭代器 […] 在内部保持其状态,因此似乎没有办法

    是的,这是有充分理由的。您不能克隆状态,否则您可能会过多地篡改生成器。

    但是,通过记住它的序列并在以后再次产生它,创建第二个迭代器是可能的,它与第一个迭代器一起运行。但是,应该只有一个迭代器真正驱动生成器 - 否则,您的哪些克隆将被允许发送 next() 参数?

    【讨论】:

    • 如果我想再次获得以前的结果,记住以前的值会有所帮助,但这不是问题的重点。我可以选择将参数记忆到next,这样我就可以从同一个生成器创建另一个迭代器并重新运行它直到同一点。这种方法的问题是 ES 中的函数不是纯函数,并且在同一生成器的第二次运行时,我可能会得到其他结果。我认为,我最好进入harmony 的邮件列表并在那里提问,如果没有人对克隆迭代器有更好的想法。
    • 也许我不太了解您的用例。你的发电机真的很纯净吗?你有没有向.next() 传递参数?两个迭代器(原始的和克隆的)是如何(通过什么)实际消耗的?
    • 我正在尝试运行以下类似于 Haskell 的非确定性单子 (ideone.com/kGF9KY) 的代码。对于数组iter.next(prev).value 的每个元素x,我需要将其作为参数传递给下一个称为next 的递归。这样yield 之后的代码会以不同的“返回值”运行多次,因此具有不确定性。
    • 我认为生成器根本不支持这样的,也许你需要备份并使用显式延续。不过我会调查一下,非确定性单子听起来很有趣。
    • 如果生成器支持它,它将窃取 Haskell 的主要特性:在不同环境中运行相同代码的能力。在我看来,启用它的最佳方法是侵入regenerator 的源代码并将其展示给 ES6 社区。这并不容易:/
    【解决方案2】:

    我为 JavaScript 编写了一个 do-notation 库,burrido。为了解决可变生成器问题,我创建了immutagen,它通过维护输入值的历史并重放它们以在任何特定状态下克隆生成器来模拟不可变生成器。

    【讨论】:

    • 甜蜜!我正在做同样的事情并来到这里:D
    【解决方案3】:

    你不能克隆一个生成器——它只是一个没有状态的函数。什么可以有状态,因此可以克隆什么,是调用生成器函数产生的迭代器

    这种方法缓存中间结果,以便克隆的迭代器可以在必要时访问它们,直到它们“赶上”。它返回一个既是迭代器又是可迭代对象的对象,因此您可以在其上调用 next 或在其上调用 for...of。可以传入任何迭代器,因此理论上您可以通过传入array.values() 在数组上克隆迭代器。无论哪个克隆在迭代中的给定点首先调用next,都会将参数传递给next(如果有的话),反映在底层生成器中yield 的值中。

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

    显然,这种方法的问题是它保留了迭代器的整个历史。一种优化是保留所有克隆迭代器的WeakMap 以及它们的进展程度,然后清理历史记录以消除所有克隆已经使用的所有过去值。

    【讨论】:

    • 很好的实现,+1!您可能还想转发 throwreturn 调用。如果你只对迭代器感兴趣,你不应该通过arg
    猜你喜欢
    • 2017-07-24
    • 2017-05-19
    • 2014-05-16
    • 2015-05-21
    • 2013-01-24
    • 1970-01-01
    • 2016-06-09
    • 2012-04-25
    相关资源
    最近更新 更多