Is there a way to write it functionally, without for
loops?
No, not really. (Of course you can always opt for recursion instead, but I'll question the usefulness of that approach).
What we're looking for are functional combinators for iterators:
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)) :-)
Now we can just treat iterators as a monad, and be no more concerned about the implementation.
As you can see, I have not used the syntax yield* xs
above which is (basically) just sugar for
for (const x of xs)
yield x;
What is looking weird in your implementation is the disparity between the outer loop and the inner non-loop. In an optimal world, there would be a yield**
syntax that did what join
does, but there's not. So we can only implement your function nicely with the above helper functions:
function* flat(x) {
if (Array.isArray(x))
yield* chain(flat)(x);
else
yield* of('foo' + x); // foreshadowing
}
or just
function flat(x) {
return Array.isArray(x) ? chain(flat)(x) : of('foo' + x);
}