Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Iterator.prototype.to and a new protocol #36

Closed
zloirock opened this issue Aug 13, 2019 · 13 comments
Closed

Iterator.prototype.to and a new protocol #36

zloirock opened this issue Aug 13, 2019 · 13 comments
Labels
good follow-on proposal this would be good but doesn't need to be in the first milestone

Comments

@zloirock
Copy link
Contributor

zloirock commented Aug 13, 2019

It could look like a new proposal, but since this proposal includes Iterator.prototype.toArray method, seems, it should be opened here and, maybe, be a part of this proposal.

So, this proposal contains .toArray, by why it's limited only to arrays? Instead of adding methods for each collections type (.toObject, .toSet, etc.), we could add one: .to(Collection) and a protocol for that.

Instead of

Object.fromEntries([1, 2, 3, 4, 5]
  .map(it => it ** 2)
  .filter(it => it % 2)
  .values()
  .map(it => [`key${it}`, it]))
// => { key0: 1, key1: 9, key2: 25 }

we could use a pipeline operator:

[1, 2, 3, 4, 5]
  .map(it => it ** 2)
  .filter(it => it % 2)
  .values()
  .map(it => [`key${it}`, it])
  |> Object.fromEntries
// => { key0: 1, key1: 9, key2: 25 }

But it's mixin of different operators and readable not very good.

In my opinion, the chaining of methods could be better:

[1, 2, 3, 4, 5]
  .map(it => it ** 2)
  .filter(it => it % 2)
  .values()
  .map(it => [`key${it}`, it])
  .to(Object)
// => { key0: 1, key1: 9, key2: 25 }

Adding .toArray to this proposal is adding the same, but only for arrays, more other, we have an additional simple way of conversion to arrays - spread operator.

So, how should work .to method? It's just passing this to the method of the passed constructor, the name of this method is a part of this protocol. So, what could be used as this protocol?

  • It could be .from method.

At this moment, it's available of Array, %TypedArray%, on Observable proposal, available proposal for adding .from method to Set, Map, WeakSet, WeakMap.
But it missed at least on Object (here used .fromEntries) and URLSearchParams.

  • It could be .fromIterable method or @@fromIterable well-known symbol.

More other, since here used iterables protocol, it could be added not only to Iterator.prototype but also to prototypes of all iterables, but it's not a part of this proposal...

[1, 2, 3, 2, 1].to(Set).map(it => [`key${it}`, it]).to(Map);

So, something like that:

Symbol.fromIterable = Symbol('Symbol.fromIterable');
Symbol.fromAsyncIterable = Symbol('Symbol.fromAsyncIterable');
const TypedArray = Object.getPrototypeOf(Int8Array);

for (const C of [Array, TypedArray, Map, Set, URLSearchParams, Iterator]) {
  C.prototype.to = function to(Constructor) {
    return Constructor[Symbol.fromIterable](this);
  };
}

AsyncIterator.prototype.to = async function to(Constructor) {
  if (Constructor[Symbol.fromAsyncIterable]) {
    return Constructor[Symbol.fromAsyncIterable](this);
  } return Constructor[Symbol.fromIterable](await AsyncIterator.from(this).toArray());
};

Object[Symbol.fromIterable] = function (iterable) {
  return this.fromEntries(iterable);
};

for (const C of [Array, TypedArray, Iterator, AsyncIterator]) {
  C[Symbol.fromIterable] = C.from;
}

for (const C of [Map, Set, WeakMap, WeakSet, URLSearchParams]) {
  C[Symbol.fromIterable] = function (iterable) {
    return new this(iterable);
  };
}

AsyncIterator[Symbol.fromAsyncIterable] = AsyncIterator.from;
@zloirock
Copy link
Contributor Author

One more argument: methods like .toArray() or .toSet() are not subclassing friendly, but here we can do something like .to(MyAwesomeArray) or .to(MyAwesomeSet).

@ljharb
Copy link
Member

ljharb commented Aug 13, 2019

You can pass the iterator directly into your subclass if that's what you want; i don't think we need to be subclassing friendly when [...iterator] produces an Array.

@zloirock
Copy link
Contributor Author

@ljharb did you see examples? The main concern here is the readability of code, from left to right, without constant search in the code where what is passed, mixed operators and different names of generating methods. Now, this proposal has it only for arrays (.toArray), but I propose to extend it to the rest kind of collection (Object, Set, etc.). Friendliness to subclassing is just a free bonus.

@ljharb
Copy link
Member

ljharb commented Aug 13, 2019

The way to convert to an object is Object.fromEntries; to a Map or Set is with the constructor.

I'd say that you'd either need the pipeline proposal (at which point, |> Array.from works too), or you'd need a new proposal that adds a generic construction protocol.

@bakkot
Copy link
Collaborator

bakkot commented Aug 13, 2019

See #17.

@zloirock
Copy link
Contributor Author

zloirock commented Aug 13, 2019

@ljharb I already wrote the same, please, re-read it one more time: the main concern in the readability.

@zloirock
Copy link
Contributor Author

@bakkot it's not the same, however, it's related, yes, thanks.

@ljharb
Copy link
Member

ljharb commented Aug 13, 2019

I don't think such a protocol, nor an ever-expanding list of "toFoo" methods, should be part of this proposal. I think this proposal should continue with toArray only, and a new proposal can be made for either of those.

@devsnek
Copy link
Member

devsnek commented Aug 13, 2019

toArray is just collect renamed, which was already discussed in #17.

@devsnek devsnek closed this as completed Aug 13, 2019
@zloirock
Copy link
Contributor Author

I didn't insist on adding it to this proposal. But this proposal includes .toArray. The main concern here - why add .toArray method if instead of that could be added .to, maybe as a separate proposal? This protocol is much simpler than transducers, much simpler than the current proposal, so maybe make sense consider it as a new proposal and use here those possibilities? I don't think that it's a good idea to add to the language both - .toArray() and .to(Array).

@bathos
Copy link

bathos commented Aug 13, 2019

It seems to me that the responses so far have not addressed what zloirock actually proposed. He’s not suggesting a proliferation of to/from methods, but rather a unification of those which already exist (and their construct-only equivalents) through a well-known symbol protocol. It’s a pretty interesting premise imo.

@domenic
Copy link
Member

domenic commented Aug 13, 2019

Array is JavaScript's fundamental collection type; it is privileged in various ways, e.g. destructuring syntax, indexed property access, etc. Hopefully you can understand how including the ability to convert to it, as a first-class method, is a high priority for the committee.

A future proposal could work on some generic collection creation/collecting/transduction protocol. That would be evaluated independently of the value of toArray(). It is not part of this proposal.

@hax
Copy link
Member

hax commented Jul 4, 2022

@zloirock I agree with the last comment, Array is very special so only adding toArray is ok.

But I also agree iter.helper().chain().to(Type) has better readability (than pipe). This could be the use case of Extensions proposal or call-this proposal, which could provide syntax like iter.chain~>to(Type) (call-this) or iter.chain::to:Type (extensions).

@michaelficarra michaelficarra added the good follow-on proposal this would be good but doesn't need to be in the first milestone label Jun 14, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
good follow-on proposal this would be good but doesn't need to be in the first milestone
Projects
None yet
Development

No branches or pull requests

8 participants