从从数组创建的生成器列表中产生

yield from a list of generators created from an array

我有这个递归生成器

var obj = [1,2,3,[4,5,[6,7,8],9],10]

function *flat(x) {
    if (Array.isArray(x))
        for (let y of x)
            yield *flat(y)
    else
        yield 'foo' + x;

}

console.log([...flat(obj)])

它工作正常,但我不喜欢 for 部分。有没有办法在功能上编写它?我试过了

if (Array.isArray(x))
   yield *x.map(flat)

没用。

有没有一种方法可以不用 for 循环来编写上述函数?

您可以将数组缩减为生成器。但这对我来说看起来比 for 循环更糟糕(虽然是功能性的:))

var obj = [1, 2, 3, [4, 5, [6, 7, 8], 9], 10]

function* flat(x) {
  if (Array.isArray(x))
    yield * x.reduceRight(
      (f, y) => function*() {
        yield * flat(y);
        yield * f()
      },
      function*() {}
    )()
  else
    yield 'foo' + x;

}

console.log([...flat(obj)])

您可以使用 rest parameters ... 并检查剩余数组的长度,以便再次调用生成器

function* flat(a, ...r) {
    if (Array.isArray(a)) {
        yield* flat(...a);
    } else {
        yield 'foo' + a;
    }
    if (r.length) {
        yield* flat(...r);
    }
}

var obj = [1, 2, 3, [4, 5, [6, 7, 8], 9], 10];
console.log([...flat(obj)])
.as-console-wrapper { max-height: 100% !important; top: 0; }

一种类似的方法,但使用 spread 生成器来调用具有展开值的移交生成器。

function* spread(g, a, ...r) {
    yield* g(a);
    if (r.length) {
        yield* spread(g, ...r);
    }
}

function* flat(a) {
    if (Array.isArray(a)) {
        yield* spread(flat, ...a);
    } else {
        yield 'foo' + a;
    }
}

var obj = [1, 2, 3, [4, 5, [6, 7, 8], 9], 10];
console.log([...flat(obj)])
.as-console-wrapper { max-height: 100% !important; top: 0; }

可能类似于

var obj = [1, 2, 3, [4, 5, [6, 7, 8], 9], 10];

function* flat(x) {
    if (Array.isArray(x)) {
        yield x.map(v => {
            return [...flat(v)].join();
        });
    } else yield "foo" + x;
}

console.log([...flat(obj)]);

map 是个好主意,但您需要将生成的生成器对象数组减少为一个生成器对象:

function *flat(x) {
    if (Array.isArray(x)) 
        yield *x.map(flat).reduce((a, b) => function*() { yield *a; yield *b }());
    else
        yield 'foo' + x;
}

var obj = [1,2,3,[4,5,[6,7,8],9],10];
console.log([...flat(obj)]);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Is there a way to write it functionally, without for loops?

不,不是真的。 (当然你总是可以选择递归,但我会质疑这种方法的用处)。

我们正在寻找的是迭代器的功能组合器:

function* of(x) { // also known as `pure` or `return`
    yield x;
}
function map(f) { return function* (xs) { // also known as `fmap`
    for (const x of xs)
        yield f(x);
}
function* join(xss) { // also known as `concat` (not `append`!) or `flatten` (but non-recursive!)
    for (const xs of xss)
        for (const x of xs)
            yield x;
}
function chain(f) { return function* (xs) { // also known as `concatMap` or `bind`
    for (const x of xs)
        const ys = f(x);
        for (const y of ys)
            yield y;
}
// or const chain = f => compose(concat, map(f)) :-)

现在我们可以将迭代器视为一个 monad,而不用再关心实现。

如您所见,我没有使用上面的语法 yield* xs,它(基本上)只是糖分

for (const x of xs)
    yield x;

在您的实现中看起来很奇怪的是外部循环和内部非循环之间的差异。在一个最佳世界中,会有一个 yield** 语法 join 做的事情,但没有。所以我们只能用上面的辅助函数来很好的实现你的功能:

function* flat(x) {
    if (Array.isArray(x))
        yield* chain(flat)(x);
    else
        yield* of('foo' + x); // foreshadowing
}

或者只是

function flat(x) {
    return Array.isArray(x) ? chain(flat)(x) : of('foo' + x);
}