DEV Community

ZaneHannanAU
ZaneHannanAU

Posted on

Iterators and Generators - playing the prototpe

Playing the Prototype

Well, may as well.

Iterators and Generators are not new concepts. Nor is modifying the prototype. But rarely are both done.

So, let's get to it!

I'm using typescript here; but the way it's written it'd be a better shim in js.

let prot = ((function*(){}).prototype as unknown as any).__proto__ as any

It's ugly, but it gives us the global Generator prototype. Asserting this by other means can be annoying.

for (let fn of gens) Object.defineProperty(prot, fn.name, {value: fn})

… Yeah, using generators to generate generators. As if it were that easy…

let gens = [
  function* map<U, T>(this: IterableIterator<U>, cb: (value: U, iteration: number) => T): IterableIterator<T> {
    let iteration = 0
    for (const value of this) yield cb(value, iteration++)
  },
  function* take<T>(this: IterableIterator<T>, num: number): IterableIterator<T> {
    if (num < 1) return;
    for (const t of this) {
      yield t;
      if (--num < 1) break
    }
  },
  function* takeWhile<T>(this: IterableIterator<T>, condition: (value: T, iteration: number) => boolean): IterableIterator<T> {
    let iteration = 0
    for (const t of this) {
      if (condition(t, iteration++)) yield t
      else break
    }
  },
  function* filter<U, T = Exclude<"" | 0 | null | undefined, U>>(this: IterableIterator<U>, cb: (value: U, iteration: number) => boolean = v => !!v): IterableIterator<T> {
    let iteration = 0
    for (const value of this) if (cb(value, iteration++)) yield value as unknown as T
  },
  function fold<U, T>(this: IterableIterator<U>, init: T, cb: (acc: T, curr: U, iteration: number) => T) {
    let iteration = 0
    for (const c of this) init = cb(init, c, iteration++)
    return init
  },
  // Iterates until both are complete.
  function* zip<A, B>(this: IterableIterator<A>, other: IterableIterator<B>): IterableIterator<[A, B]> {
    try {
      let a = this.next(), b = other.next()
      while (!(a.done || b.done)) {
        yield [a.value, b.value]
        a = this.next()
        b = this.next()
      }
    } finally {
      this.return()
      other.return()
    }
  },
  function* enumerate<T>(this: IterableIterator<T>): IterableIterator<[number, T]> {
    let iteration = 0
    for (const t of this) yield [iteration++, t]
  },
  function unzip<A, B>(this: IterableIterator<[A | undefined, B | undefined]>): [A[], B[]] {
    let a = [], b = []
    for (const [aa, bb] of this) {
      if (aa !== undefined) a.push(aa);
      if (bb !== undefined) b.push(bb)
    }
    return [a, b]
  },
  function collect<T>(this: IterableIterator<T>): T[] {
    return Array.from(this)
  }
]

All of these are based on std::iter::Iterator from rust (13.2 MiB), and all use only the native js functions. Now you can write your infinite loop and have it go lazily … I guess.

function* idgen(i = 0) {while(1) yield i++}
for (let value of idgen().filter(v => v % 3 == 0).map(v => v * v).take(32)) console.log(value)
0
9
36
81
144
225
324
441
576
729
900
1089
1296
1521
1764
2025
2304
2601
2916
3249
3600
3969
4356
4761
5184
5625
6084
6561
7056
7569
8100
8649

Top comments (0)