Closed Jack-Works closed 1 year ago
I remain strongly against the class approach, and the notes don't make it clear to me what the persuasive argument was (nor what future extensibility you have in mind, or why that extensibility can't be achieved with the function approach). Per the "base class" question you raise, it feels to me like extensibility is harder with a class hierarchy due to the fragile base class problem.
New classes shouldn't be callable, since class
can't be callable. I agree that if it's a constructor, its name should follow the almost universal convention of being in PascalCase (ie, Range, not range).
I'd be very interested to see the results of an informed investigation into various designs.
nor what future extensibility you have in mind, or why that extensibility can't be achieved with the function approach
To extends, means helper methods. Yes, they can be added in the current design but as you can see, it is adding methods on the RangeIteratorPrototype
which makes the semantics of helper methods ambiguous. (If we have a .contains()
, should it consume the iterator since it is on the iterator prototype?). Another thing to mention: the current design is adding internal slots on a built-in generator.
New classes shouldn't be callable, since class can't be callable.
If so, I'll choose the 3.i.b (hidden class, exposed create helper) route to make the change (at least for now) so I can bypass the PascalCase requirement (also, at least for now, I'd be able to change if it is found harmful or not welcomed design).
A RangeIteratorPrototype
is possible with either design, so I'm not clear on why extensibility there has any effect on class vs factory.
When you say "hidden class", if the constructor is reachable, it'd need to have a PascalCase name. If it's not reachable, then it seems like it's just the factory approach, where range
is a function that produces an iterator (that inherits from RangeIteratorPrototype)?
A RangeIteratorPrototype is possible with either design
Yes, I have mentioned that "which makes the semantics of helper methods ambiguous. (If we have a .contains(), should it consume the iterator since it is on the iterator prototype?)."
When you say "hidden class", if the constructor is reachable, it'd need to have a PascalCase name. If it's not reachable, then it seems like it's just the factory approach
It's not the case. I'm meaning (the second approach):
const r = Number.range(0, 1)
const hiddenRangeCtor = r.constructor
assert(hiddenRangeCtor.name === 'NumberRange') // or other name
assert(r instanceof hiddenRangeCtor)
assert(!(r instanceof Number.range))
Ah, I see what you mean. In either case, yes, I would assume a generic .contains()
consumes the iterator, but that a Range-specific contains
need not.
Thanks, your code sample matches my expectation if we were to go with the class instance approach. Presumably in that case we'd make hiddenRangeCtor
also non-constructible (or not accessible via .constructor
), so you couldn't do new hiddenRangeCtor
?
Presumably in that case we'd make
hiddenRangeCtor
also non-constructible (or not accessible via.constructor
), so you couldn't donew hiddenRangeCtor
?
I think it is no harm to get the underlying Range class and construct it manually. My expectation is that: Number.range = (...args) => new #hiddenNumberRangeCtor#(...args)
to improve ergonomics of the API.
@codehag hi! Could you give help to use Cognitive Dimensions of Notation to investigate which semantics is better? Current semantics VS #42. It seems like both Iterator and Iterables have their supporters and it's hard to going on now.
cc @Felienne
This looks interesting. I want to think about it a bit, but wanted to make sure Felienne also saw.
In the MDN Iterator example the authors aptly named the function makeRangeIterator
as to highlight that the result of that operation would be an iterator and not a data structure (Iterable).
From my experience with other libraries: Number.range(0, 100, 2)
suggests to return a data structure (lodash). To suggest an Iterator I would be looking at something like Iterator.numberRange(0, 100, 2)
or shorter Iter.range(0, 100, 2)
.
While I think that having an Iterator is a perfectly fine simple step, I think there can be an argument made for a data structure: doing an .includes
when having a data structure allows for a O(1)
complexity (sample implementation) while doing an .includes
on an Iterable would be a O(N)
complexity.
Adding both Iterator.range
and Number.range
seems not necessary. I'm wondering if we choose the name as Iterator.range
, does it release the harm in of not-reusbility?
Iterator.range
kind of kills the ability to support multiple number types - ie, BigInt, or a future Decimal.
One would need to get creative about naming: Iterator.bigIntRange
but there might be another name that is better suited?
Aside from this, I still think that there are enough arguments for having an Iterable over a Iterator.
@martinheidegger i don't agree with that last point; i still haven't heard any arguments that hold. There's lots of discussion about that on #42, though, so let's have that there.
Reference: https://github.com/tc39/incubator-agendas/blob/master/notes/2020/08-10.md
new
op to create).Array
vsnew Array
)new
the class in the backend.I have no enough free time to work on this these days so sorry for the delay (it's nearly a whole month since the incubator call).
Generally, my next step is to migrate the current design with no observable API style changes (e.g. from
Number.range
tonew Number.range
(and I don't like that)). That means I will choose one of 3.i.a (callable class) or 3.i.b (hidden class, exposed create helper). Please leave comments on which side you'd like to see.