I've got this recursive generator
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)])
It works fine, but I don't like the for
part. Is there a way to write it functionally? I tried
if (Array.isArray(x))
yield *x.map(flat)
which didn't work.
Is there a way to write the above function without for
loops?
It returns only a single value to the caller, and the code execution stops as soon as it reaches the return statement. When a caller calls the generator function, the first yield is executed, and the function stops. It then returns the generator object to the caller where the value is stored.
The yield keyword converts the expression given into a generator function that gives back a generator object. To get the values of the object, it has to be iterated to read the values given to the yield.
A return statement in a generator, when executed, will make the generator finish (i.e. the done property of the object returned by it will be set to true ). If a value is returned, it will be set as the value property of the object returned by the generator.
yield can be used inside a generator function & it helps to pause and resume a function at any time asynchronously. Also Additionally it helps to return value from a generator function. Check this document for more information. call is redux-saga effects which help to make asynchronous calls.
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);
}
You could use rest parameters ...
and check the length of the rest array for another calling of the generator
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; }
A similar approach but with a spread
generator for calling the handed over generator with the spreaded values.
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; }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With