Closed rbuckton closed 1 year ago
I'm trying to avoid adding a global name on the globalThis
but if everyone is okay with that, I'm also okay to change the API design.
On the other hand, the slice notation can behave the following way
// Literal `Range` syntax:
let range = 1:3;
// -> range = Number.range(1, 3);
And I will add @@slice
, @@geti
on the return value. (Currently, it is immutable so @@seti
won't be involved)
I'm opposed to a syntax that would require a new
just to create a range; it's visual noise that distracts from what should be a lightweight operation. If the function can be called without a new
, then I have no opinion on whether it's an object or just a plain iterator.
There's no reason Interval(1, 3)
couldn't be synonymous with new Interval(1, 3)
. If we had value types, I imagine something like Interval
would be a value type (in which case the new
would be unnecessary/would result in boxing like new Boolean
vs Boolean
).
Value type for range? Do you mean the record and tuple or a separate new value type? (I don't think it should be a value type)
If so, I would prefer NumberSequence
or NumberStream
. So you can make functions like:
NumberSeq.range(from, to, step = 1)
, NumberStream.range(to)
;NumberSeq.count()
// output [0, 1, 2, 3, ...]NumberSeq.iterate(initial, iterateFunc)
// output [initial, iterateFunc(initial), iterateFunc(iterateFunc(initial)), ...]`NumberSeq.repeat(seed)
// output [...seed, ...seed, ...seed, ...], or [seed, seed, seed, ...]But not need to limit to NumberRange
.
We can also make the class work for non-numeric types, and rename it as Sequence
or Stream
. This may be too far from what this spec want to do. But API like this make more sense to me.
I like the idea of being value type. It allow us use ===
(for example, ^1 === ^1
could return true
if Index
is value type)
I also like the idea of range.includes()
, but it doesn't require range to be value type, so it could be a separate issue. I believe #17 already cover that.
I think we still need separate classes (or value types) for different range (interval), aka. NumberRange
, BigIntRange
, IndexRange
. Note they could be Number.Range
, BigInt.Range
, Index.Range
so we don't need adding too many names to globalThis.
Consider possible range literals, (1n:3n)
should be BigInt.Range(1n, 3n)
, (1:^1)
should be Index.Range(Index(1), Index(1, {fromEnd:true})
, but what about (1:3)
, whether it create Number.Range
or Index.Range
?
Value types are pretty far out. If we had them now, it could make sense. I think this proposal fills an urgent need, and we shouldn't wait on value types for them. I think this proposal will be very very useful with range iterables (if we have them at all) being objects. Most of the time, you won't really use the iterable much at all; you'll just get its iterator. The Temporal proposal is making a similar tradeoff.
What would happen on property access with this new built in type?
const r = Range(1, 3);
[1, 2, 3, 4, 5][r]
// [2, 3] or undefined?
(Note your code have ASI problem)
I don't think we should overwrite the behavior of [ ]. (I think this belong to the slice notation proposal)
(I think this belong to the slice notation proposal)
Why?
Maybe things like [r.from:r.to] or other notation. I'm not preferring to overload the [expr]
I'm not preferring to overload the [expr]
I agree.
I'm not sure we'd want different behavior for slice notation depending on where it's used. Not a fan of this:
const r = 1: 3; // creates a Range object
[1, 2, 3, 4, 5][r]; // undefined
[1, 2, 3, 4, 5][1: 3]; // [2, 3]
I'd prefer this:
const f = 1:3; // SyntaxError
const r = Range(1, 3);
[1, 2, 3, 4, 5][r]; // undefined
[1, 2, 4, 5, 5][r.from: r.to]; // [2, 3]
[1, 2, 3, 4, 5][1: 3]; // [2, 3]
Perhaps as part of a different proposal, it would be good to introduce some sort of getter/setter protocol. If Array.prototype[Symbol.getter]
exists, it is called and gets passed whatever is between []
. If not it does regular property access. It add more extensibility to the language.
late to this issue, but wanted to re-up the notion of a .includes
method. ~I'm opposed to the idea of reusing a range, but I think that's not necessarily a problem; for example, Rust's iterator semantics allow for a .includes
method, which simply consumes the iterator. I don't see why we couldn't do that here, as a way to allow this convenience method without cracking open the reusability discussion.~
EDIT: striking discussion on reusability, as I think it's actually just orthogonal to this issue. regardless, curious to hear thoughts on .includes
specifically.
I'm not sure what you mean by ".includes" - you mean a method on the iterator itself?
curious to hear thoughts on
.includes
specifically.
What would be the semantics of an includes
method? Would it consume the iterator? Might it only compare a searchValue
with the range endpoints, or should it take the step
value in to account and return true
only for a searchValue
that would be yielded? Would it, like Array#includes
, use SameValueZero
equality?
proposal-iterator-helpers has a spec for %Iterator.prototype%.some
. I think it would be least surprising if an iterator's includes
method — wherever specified — were functionally equivalent to
proto.includes = function (searchValue) {
return this.some(value => SameValueZero(value, searchValue));
};
If the semantics are likely to be different than those that might be specified elsewhere, it may be preferable to consider using a different name for the suggested method.
I'm not sure what you mean by ".includes" - you mean a method on the iterator itself?
yep, like Number.range(0, 10).includes(5)
What would be the semantics of an includes method? Would it consume the iterator?
I think yes, it would have to consume the iterator.
Might it only compare a searchValue with the range endpoints, or should it take the step value in to account and return true only for a searchValue that would be yielded?
I could see this going either way. should Number.range(0, 10).includes(5.5)
return true
? I'm not sure on which side I fall there.
Would it, like Array#includes, use SameValueZero equality?
I think so, yes.
I actually wasn't aware of the iterator helpers proposal - %Iterator.prototype%.some
does seem like a reasonable possibility here, in which case there's nothing to be added to this proposal. but would the other semantic - i.e. simply checking that the search value lies within the bounds of the iterator, with no regard for the step size - be useful? if so, perhaps that would be useful here (and perhaps under a different name).
but would the other semantic - i.e. simply checking that the search value lies within the bounds of the iterator, with no regard for the step size - be useful? if so, perhaps that would be useful here (and perhaps under a different name).
I think it would be slightly more useful to instead have something like
Math.inInterval = function (number, inclusive, exclusive) {
if (inclusive < exclusive) {
return inclusive <= number && number < exclusive;
}
return exclusive < number && number <= inclusive;
};
This, or something similar, could be specified in https://github.com/rwaldron/proposal-math-extensions
We would then have
var range = Number.range(0, 10);
Math.inInterval(5.5, range.start, range.end); // true
Note (2022-07-14): there is a proposal for a similar function at https://github.com/js-choi/proposal-math-between
This, or something similar, could be specified in https://github.com/rwaldron/proposal-math-extensions
Yes, I agree, but the includes
that will consume the iterator can be also specified in the iterator helper proposal.
Consider slice notation seems have trouble to advance, I think we could reconsider the ideas of range[i]
or range.item(i)
(random access like array, at least python range has that). Note current semantic allow us have O(1) performance and don't rely on iterator. Similarly, includes(x)
could also be O(1) and don't rely on iterator.
What would happen on property access with this new built in type?
Sometimes it is very handy to get range of array, but overloading [] is too much, maybe it would be nice for Interval/range to be callable, like:
const rows = [ ['a','b','c','d'], ['d','e','f','g'], ['q','w','e','r']];
(1..2)(rows) == [['d','e','f','g'], ['q','w','e','r']];
rows.map(1..2) == [ ['b','c'], ['e','f'], ['w','e']];
I think a range should be in Number, because we are talking about use a range of numbers, create a new class of Interval could be too much just to a range.
looks like a first-class value is very far from us... closing this for now but still welcome discussions
In https://github.com/tc39/proposal-slice-notation/issues/19#issuecomment-415995994 I suggested adding something similar to the C#
Index
andRange
types which could also address these use cases with a more flexible API than an overloadedNumber.range
method.An example of these APIs can be found here:
Index
- https://github.com/esfx/esfx/blob/c50876e542bc73df907b5e3ff4ef95ad656cb3b3/packages/interval/src/index.ts#L23Interval
- https://github.com/esfx/esfx/blob/c50876e542bc73df907b5e3ff4ef95ad656cb3b3/packages/interval/src/index.ts#L107The advantage of this (or a similar) API is that it provides a convenient place to attach functionality like
.includes(value)
, as well as a place to provide different ways to generate anInterval
without relying on possibly confusing overloaded behavior likeNumber.range(10)
.