You can significantly simplify by using a generator function, something like this:
const range = (start, stop) => { if (stop === undefined) { stop = start start = 0 } return { *[Symbol.iterator]() { for (let n = start; n < stop; ++n) yield n }, } }
You can also write custom functions for map, filter, reduce, etc. that will operate lazily on iterables:
map
filter
reduce
const map = (fn) => (iter) => ({ *[Symbol.iterator]() { for (const x of iter) yield fn(x) }, }) const filter = (fn) => (iter) => ({ *[Symbol.iterator]() { for (const x of iter) { if (fn(x)) yield x } }, }) const reduce = (init, fn) => (iter) => ({ *[Symbol.iterator]() { let acc = init for (const cur of iter) { yield (acc = fn(acc, cur)) } }, })
Laziness in action:
const log = x => (console.log(x), x) for (const x of map(log)(range(Number.MAX_SAFE_INTEGER))) { if (x >= 3) break // only logs 0, 1, 2, 3 }
If you tried to do that with an array, you'd run out of memory.
Your code is really weird you mix iterator protocol with generators. You just can use this, it will do the same:
function* range(start, stop) { if (stop === undefined) { stop = start start = 0 } for (let n = start; n < stop; ++n) yield n; }
I've also written an article on higer order iterators including async but in Polish (you can use translate option to read it).
Generatory i Iteratory wyższego poziomu
Not weird at all, it's a common pattern. That way, you can easily extend your range objects with other properties, like min, max, includes, etc.
range
min
max
includes
More importantly, it also ensures you don't accidentally mutate the range and get unexpected results. With your version:
const r = range(1, 10) ;[...r] // [1, 2, 3, 4, 5, 6, 7, 8, 9], as expected ;[...r] // [], empty array, because the iterator has already been consumed
With my version:
const r = range(1, 10) ;[...r] // [1, 2, 3, 4, 5, 6, 7, 8, 9], as expected ;[...r] // [1, 2, 3, 4, 5, 6, 7, 8, 9], same as before
You can still get a mutable iterator from my version if you really need one, though. You just have to ask for it explicitly:
const i = range(1, 10)[Symbol.iterator]() ;[...i] // [1, 2, 3, 4, 5, 6, 7, 8, 9] ;[...i] // []
Hey lionel, thanks for the great feedback. The idea of using generators looks really interesting. I'll look into it and maybe do a follow up article on Generators.
What you get with a generator function is basically what you hand-rolled with return { value, done }:
return { value, done }
const gen = (function*() { yield 1 })() // Object [Generator] {} gen.next() // { value: 1, done: false } gen.next() // { value: undefined, done: true }
They're pretty nifty 😊
Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment's permalink.
Hide child comments as well
Confirm
For further actions, you may consider blocking this person and/or reporting abuse
We're a place where coders share, stay up-to-date and grow their careers.
You can significantly simplify by using a generator function, something like this:
You can also write custom functions for
map
,filter
,reduce
, etc. that will operate lazily on iterables:Laziness in action:
If you tried to do that with an array, you'd run out of memory.
Your code is really weird you mix iterator protocol with generators. You just can use this, it will do the same:
I've also written an article on higer order iterators including async but in Polish (you can use translate option to read it).
Generatory i Iteratory wyższego poziomu
Not weird at all, it's a common pattern. That way, you can easily extend your
range
objects with other properties, likemin
,max
,includes
, etc.More importantly, it also ensures you don't accidentally mutate the
range
and get unexpected results. With your version:With my version:
You can still get a mutable iterator from my version if you really need one, though. You just have to ask for it explicitly:
Hey lionel, thanks for the great feedback.
The idea of using generators looks really interesting. I'll look into it and maybe do a follow up article on Generators.
What you get with a generator function is basically what you hand-rolled with
return { value, done }
:They're pretty nifty 😊