Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement Symbol.iterator?

I'm trying to create a subclass of Set, and since I cannot just simply extend from it, I'm wrapping its functionality.

I'm trying to implement the Symbol.iterator method, but Flow is having none of it.

This is the code that I have:

/* @flow */
class CSet<T> {
    _set: Set<T>;
    [Symbol.iterator](): Iterator<T> {
        return this._set[Symbol.iterator];
    }
}

var a: CSet = new CSet();
for(var b of a){

}

core.js:309:5,29: property @@iterator
Property not found in
test.js:2:7,10: CSet

test.js:4:2,6:2: 
computed property keys not supported

The second error isn't as huge of a deal since I can easily suppress it. I'm wondering if I'm just doing something wrong all-together though.

like image 830
Kyle Avatar asked Aug 11 '15 13:08

Kyle


3 Answers

Because Flow doesn't currently have general support for symbols, the way it represents Symbol.iterator is hacky and basically prevents the ability to define iterators in userspace for now (its support only works in library definitions) :(

Specifically Flow expects that an iterable has a @@iterator property on them (which is certainly not a valid property name -- but this was a temporary hack to get support in library definitions).

So until proper symbol support lands, your best bet for a workaround here would be to create a library definition for this module that uses this @@iterator property for Flow's understanding:

// RealModule.js
export class CSet {
    [Symbol.iterator]() {
        return this._set[Symbol.iterator];
    }
}

.

// RealModule.FlowLibDef.js
declare module RealModule {
    declare class CSet<T> {
        _set: Set<T>;
        @@iterator(): Iterator<T>;
    }
}
like image 197
JeffMo Avatar answered Sep 22 '22 20:09

JeffMo


// @flow
class MyCollection<T> {
    /*:: @@iterator(): Iterator<T> { return ({}: any); } */

    // $FlowFixMe: computed property
    [Symbol.iterator](): Iterator<T> {
        // NOTE: this could just as easily return a different implementation
        return new MyIterator(this);
    }
}

class MyIterator<+T> {
    /*:: @@iterator(): Iterator<T> { return (this: any); } */

    // $FlowFixMe: computed property
    [Symbol.iterator](): Iterator<T> {
        return this;
    }

    next(): IteratorResult<T, void> {
        return { done: false, value: someT };
        // or return { done: true, value: undefined };
    }
}

for (const value of new MyCollection()) {
    console.log(value);
}

The reason this works is that flow interprets /*:: code */ as though it were flow code in the source, except that it is commented out at runtime therefore does not actually affect the code.

Flow intrinsically knows about the @@iterator method, despite it not being valid JavaScript, thus we define it as existing, returning an Iterator<T>, and returning a value that works for it (i.e. an empty object cast as any).

The computed property method is then ignored completely by flow, as if it were not defined at all. It is crucial that you actually return a valid Iterator<T> from the method, otherwise things will break at runtime.

like image 35
ckknight Avatar answered Sep 20 '22 20:09

ckknight


I've found a trick that lets you mark a user-defined class as iterable, without needing to write and maintain a parallel libdef for your entire class.

The key is to write a dedicated superclass that implements [Symbol.iterator](), and provide a libdef just for that superclass:

// IterableBase.js
export default class IterableBase {
  [Symbol.iterator]() {
    return this._Symbol_iterator();
  }
}
// IterableBase.js.flow
// @flow
declare class IterableBase<T> {
  @@iterator(): Iterator<T>;
}
export default IterableBase;

Now you can have your custom class extend IterableBase and implement the substitute method name:

// RealModule.js
// @flow
import IterableBase from './IterableBase';

class CSet<T> extends IterableBase<T> {
  _Symbol_iterator(): Iterator<T> {
    ...
  }
}

This is obviously still a hack, but it should be easier and safer than the alternative.

like image 34
Stuart Cook Avatar answered Sep 20 '22 20:09

Stuart Cook