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

Consider first-class type (i.e. 'NumberRange' or 'Interval') vs. a 'range' method #22

Closed
rbuckton opened this issue Apr 1, 2020 · 23 comments

Comments

@rbuckton
Copy link

rbuckton commented Apr 1, 2020

In tc39/proposal-slice-notation#19 (comment) I suggested adding something similar to the C# Index and Range types which could also address these use cases with a more flexible API than an overloaded Number.range method.

An example of these APIs can be found here:

The 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 an Interval without relying on possibly confusing overloaded behavior like Number.range(10).

@Jack-Works
Copy link
Member

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)

@tabatkins
Copy link

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.

@rbuckton
Copy link
Author

rbuckton commented Apr 4, 2020

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).

@Jack-Works
Copy link
Member

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)

@tiansh
Copy link

tiansh commented Apr 8, 2020

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.

@hax
Copy link
Member

hax commented Apr 22, 2020

  1. I like the idea of being value type. It allow us use === (for example, ^1 === ^1 could return true if Index is value type)

  2. 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 Make Number.range() return re-usable value #17 already cover that.

  3. 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.

  4. 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?

@littledan
Copy link
Member

littledan commented Apr 27, 2020

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.

@gsathya
Copy link
Member

gsathya commented Jun 9, 2020

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?

@Jack-Works
Copy link
Member

(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)

@gsathya
Copy link
Member

gsathya commented Jun 9, 2020

(I think this belong to the slice notation proposal)

Why?

@Jack-Works
Copy link
Member

Jack-Works commented Jun 9, 2020

Maybe things like [r.from:r.to] or other notation. I'm not preferring to overload the [expr]

@gsathya
Copy link
Member

gsathya commented Jun 10, 2020

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]

@obedm503
Copy link

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.

@mpcsh
Copy link
Member

mpcsh commented Jun 15, 2020

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.

@ljharb
Copy link
Member

ljharb commented Jun 15, 2020

I'm not sure what you mean by ".includes" - you mean a method on the iterator itself?

@Andrew-Cottrell
Copy link

Andrew-Cottrell commented Jun 16, 2020

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.

@mpcsh
Copy link
Member

mpcsh commented Jun 16, 2020

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).

@Andrew-Cottrell
Copy link

Andrew-Cottrell commented Jun 17, 2020

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

@Jack-Works
Copy link
Member

Jack-Works commented Jun 17, 2020

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.

@hax
Copy link
Member

hax commented Jul 22, 2020

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.

@stiff
Copy link

stiff commented Jan 20, 2021

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']];

@Jbnado
Copy link

Jbnado commented Mar 1, 2023

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.

@Jack-Works
Copy link
Member

looks like a first-class value is very far from us... closing this for now but still welcome discussions

@Jack-Works Jack-Works closed this as not planned Won't fix, can't repro, duplicate, stale Mar 22, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet