Implement Iterable interface with default array iterator

Viewed 307

I have a Vector class that implements a Tuple, like this (simplified code, the Tuple interface is basically just an array without methods and a fixed size N):

interface Tuple<T, N extends number> extends ArrayLike<T>, Iterable<T> {
    0: T
    length: N
}

export class Vector<N extends number> implements Tuple<number, N> {
    //Implementation
}

Now I want to make my Vector iterable. When I create a variable of type Tuple, it is already iterable, but the Vector obviously isn't:

let t: Tuple<number,2> = [1,2]
for (const iterator of t) {...} //works

let v = new Vector([1,2])
for (const iterator of v) {....} //Type 'Vector<2>' must have a '[Symbol.iterator]()' method that returns an iterator.

My question is: How do I implement the "default" iterator into my vector, so I don't have to write the functions again? Can I somehow copy the Tuples or Arrays iterator? If this isn't possible, whats the best way to define my iterator? For now, I've added this (generator) method to my Vector class:

*[Symbol.iterator](): Iterator<number, any, undefined> {
       for (let i = 0; i < this.length; i++) {
           yield this[i]
       }
 }

It works, but I'm not 100% sure what it's actually doing, what the return value Iterator<number, any, undefined> specifies, and wether theres a better way to do it.

1 Answers

If you want your class to be iterable, you definitely have to provide an iterator somehow. But if you simply want to forward iteration to a property of your class, then I think you just need to implement a getter that gets the iterator from whatever you want to iterate.

For example, something like:

export class Vector<N extends number> implements Tuple<number, N> {
  constructor(public values: Tuple<number, N>) {}

  get [Symbol.iterator]() {
    return this.values[Symbol.iterator]
  }
}

And now when you iterate it, you actually get the iterator from the internal property values, to iterate over.

Playground


Is there a way to this without keeping 'values' as an object property (e.g. by assigning the Symbol.iterator directly in the constructor)?

Sure. By default the iterator property is readonly, but if you declare the iterator type in the class then you are saying it's writable. Then assign to the iterator property in the constructor.

class Vector {
  [Symbol.iterator]: () => Iterator<number>
  
  constructor(public values: number[]) {
    this[Symbol.iterator] = this.values[Symbol.iterator]
  }
}

See playground

Related