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

Normative: rename to Iterator.range #59

Merged
merged 4 commits into from
Dec 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 11 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# Number.range & BigInt.range
# Iterator.range

**Champions**: Jack Works

**Author**: Jack Works

**Stage**: 1

This proposal describes adding a `Number.range` and a `BigInt.range` to JavaScript.
This proposal describes adding a `Iterator.range` to the JavaScript.

See the rendered spec at [here](https://tc39.es/proposal-Number.range/).

Expand All @@ -16,9 +16,9 @@ See the rendered spec at [here](https://tc39.es/proposal-Number.range/).

## Polyfill

- A polyfill is available in the [core-js](https://github.com/zloirock/core-js) library. You can find it in the [ECMAScript proposals section](https://github.com/zloirock/core-js/#numberrange).
- A polyfill is available in the [core-js](https://github.com/zloirock/core-js) library. You can find it in the [ECMAScript proposals section](https://github.com/zloirock/core-js/#numberrange).

- In the proposal repo is available a [a step-to-step implementation of the proposal](./polyfill.js) [![codecov](https://codecov.io/gh/tc39/proposal-Number.range/branch/main/graph/badge.svg)](https://codecov.io/gh/tc39/proposal-Number.range) so you can verify if there is a bug in the specification by the debugger.
- In the proposal repo is available a [a step-to-step implementation of the proposal](./polyfill.js) [![codecov](https://codecov.io/gh/tc39/proposal-Number.range/branch/main/graph/badge.svg)](https://codecov.io/gh/tc39/proposal-Number.range) so you can verify if there is a bug in the specification by the debugger.

## Motivation

Expand Down Expand Up @@ -48,14 +48,14 @@ Tons of libraries providing a range: math.js, lodash, underscore.js, ramda, d3,
- - - Decimal step (0, 0.2, 0.4, ...)
- BigInt Support
- - Same as Arithmetic Sequence
- Infinite Sequence `Number.range(0, Infinity)` -> (0, 1, 2, 3, ...)
- Infinite Sequence `Iterator.range(0, Infinity)` -> (0, 1, 2, 3, ...)

### Non-goals

- New Syntax
- String Sequence (a, b, c, d, ...)
- Magic
- - E.g. `if (x in Number.range(0, 10))` (Kotlin have this feature)
- - E.g. `if (x in Iterator.range(0, 10))` (Kotlin have this feature)

### Discussions

Expand All @@ -79,21 +79,21 @@ See [tests](./__tests__/test.js) to learn about more usages.
```js
for (const i of BigInt.range(0n, 43n)) console.log(i) // 0n to 42n

// With iterator helper proposal
Number.range(0, Infinity)
Iterator.range(0, Infinity)
.take(1000)
.filter((x) => !(x % 3))
.toArray()

function* even() {
for (const i of Number.range(0, Infinity)) if (i % 2 === 0) yield i
for (const i of Iterator.range(0, Infinity)) if (i % 2 === 0) yield i
}
;[...Number.range(1, 100, 2)] // odd number from 1 to 99
;[...Iterator.range(1, 100, 2)] // odd number from 1 to 99
```

# Presentations

- 2020 Apr [Notes](https://github.com/tc39/notes/blob/main/meetings/2020-03/april-1.md#numberrange-and-bigintrange-for-stage-1) [Slides](https://docs.google.com/presentation/d/1JD9SrOEtGEviPYJ3LQGKRqDHYeF-EIt7RHB92hKPWzo/)
- [Apr 2020 Notes](https://github.com/tc39/notes/blob/main/meetings/2020-03/april-1.md#numberrange-and-bigintrange-for-stage-1) / [Slides](https://docs.google.com/presentation/d/1JD9SrOEtGEviPYJ3LQGKRqDHYeF-EIt7RHB92hKPWzo/)
- [Jul 2020 Notes](https://github.com/tc39/notes/blob/65a82252aa14c273082e7687c6712bb561bc087a/meetings/2020-07/july-22.md#numberrange-for-stage-2) / [Slides](https://docs.google.com/presentation/d/116FDDK2klJoEL8s2Q7UXiDApC681N-Q9SwpC0toAzTU/)

### Signature

Expand Down
112 changes: 39 additions & 73 deletions __tests__/test.js
Original file line number Diff line number Diff line change
@@ -1,37 +1,37 @@
/// <reference path="../global.d.ts" />
require("../polyfill.js")
require("core-js/proposals/iterator-helpers")
require("../polyfill.js")

test("Number.range", () => {
expect(that(Number.range(-1, 5))).toMatchInlineSnapshot(`"-1f, 0f, 1f, 2f, 3f, 4f"`)
expect(that(Number.range(-5, 1))).toMatchInlineSnapshot(`"-5f, -4f, -3f, -2f, -1f, 0f"`)
expect(that(Number.range(0, 1, 0.1))).toMatchInlineSnapshot(
test("Iterator.range<number>", () => {
expect(that(Iterator.range(-1, 5))).toMatchInlineSnapshot(`"-1f, 0f, 1f, 2f, 3f, 4f"`)
expect(that(Iterator.range(-5, 1))).toMatchInlineSnapshot(`"-5f, -4f, -3f, -2f, -1f, 0f"`)
expect(that(Iterator.range(0, 1, 0.1))).toMatchInlineSnapshot(
`"0f, 0.1f, 0.2f, 0.30000000000000004f, 0.4f, 0.5f, 0.6000000000000001f, 0.7000000000000001f, 0.8f, 0.9f"`
)
expect(that(Number.range(2 ** 53 - 1, 2 ** 53, { inclusive: true }))).toMatchInlineSnapshot(
expect(that(Iterator.range(2 ** 53 - 1, 2 ** 53, { inclusive: true }))).toMatchInlineSnapshot(
`"9007199254740991f, 9007199254740992f"`
)
expect(that(Number.range(0, 0))).toMatchInlineSnapshot(`""`)
expect(that(Number.range(0, -5, 1))).toMatchInlineSnapshot(`""`)
expect(that(Iterator.range(0, 0))).toMatchInlineSnapshot(`""`)
expect(that(Iterator.range(0, -5, 1))).toMatchInlineSnapshot(`""`)
})

test("BigInt.range", () => {
expect(that(BigInt.range(-1n, 5n))).toMatchInlineSnapshot(`"-1n, 0n, 1n, 2n, 3n, 4n"`)
expect(that(BigInt.range(-5n, 1n))).toMatchInlineSnapshot(`"-5n, -4n, -3n, -2n, -1n, 0n"`)
test("Iterator.range<bigint>", () => {
expect(that(Iterator.range(-1n, 5n))).toMatchInlineSnapshot(`"-1n, 0n, 1n, 2n, 3n, 4n"`)
expect(that(Iterator.range(-5n, 1n))).toMatchInlineSnapshot(`"-5n, -4n, -3n, -2n, -1n, 0n"`)
})

test("Range to infinity", () => {
{
let q = 0
for (const i of Number.range(0, Infinity)) {
for (const i of Iterator.range(0, Infinity)) {
q += i
if (i >= 100) break
}
expect(q).toMatchInlineSnapshot(`5050`)
}
{
let q = 0n
for (const i of BigInt.range(0n, Infinity, { inclusive: true, step: 2n })) {
for (const i of Iterator.range(0n, Infinity, { inclusive: true, step: 2n })) {
q += i
if (i >= 100) break
}
Expand All @@ -40,61 +40,33 @@ test("Range to infinity", () => {
})

test("Use with Iterator helpers", () => {
expect(that(Number.range(0, 10).take(5))).toMatchInlineSnapshot(`"0f, 1f, 2f, 3f, 4f"`)
expect(that(Number.range(0, 10).map((x) => x * 2))).toMatchInlineSnapshot(
expect(that(Iterator.range(0, 10).take(5))).toMatchInlineSnapshot(`"0f, 1f, 2f, 3f, 4f"`)
expect(that(Iterator.range(0, 10).map((x) => x * 2))).toMatchInlineSnapshot(
`"0f, 2f, 4f, 6f, 8f, 10f, 12f, 14f, 16f, 18f"`
)
expect(BigInt.range(0n, 10n).reduce((prev, curr) => prev + curr, 0n)).toMatchInlineSnapshot(`45n`)
expect(Iterator.range(0n, 10n).reduce((prev, curr) => prev + curr, 0n)).toMatchInlineSnapshot(`45n`)
})

test("Be an iterator", () => {
const x = Number.range(0, 10)
const x = Iterator.range(0, 10)
const iteratorPrototype = (function* () {})().__proto__.__proto__.__proto__
expect(x.__proto__.__proto__ === iteratorPrototype).toBeTruthy()
})

test("NaN", () => {
expect(that(Number.range(NaN, 0))).toMatchInlineSnapshot(`""`)
expect(that(Number.range(0, NaN))).toMatchInlineSnapshot(`""`)
expect(that(Number.range(NaN, NaN))).toMatchInlineSnapshot(`""`)

expect(that(Number.range(0, 0, { step: NaN }))).toMatchInlineSnapshot(`""`)
expect(that(Number.range(0, 5, NaN))).toMatchInlineSnapshot(`""`)
})
expect(that(Iterator.range(NaN, 0))).toMatchInlineSnapshot(`""`)
expect(that(Iterator.range(0, NaN))).toMatchInlineSnapshot(`""`)
expect(that(Iterator.range(NaN, NaN))).toMatchInlineSnapshot(`""`)

test("{from, to, step} getter", () => {
{
const a = Number.range(1, 3)
expect(a.start).toMatchInlineSnapshot(`1`)
expect(a.end).toMatchInlineSnapshot(`3`)
expect(a.step).toMatchInlineSnapshot(`1`)
expect(a.inclusive).toMatchInlineSnapshot(`false`)
}
{
const a = Number.range(-1, -3, { inclusive: true })
expect(a.start).toMatchInlineSnapshot(`-1`)
expect(a.end).toMatchInlineSnapshot(`-3`)
expect(a.step).toMatchInlineSnapshot(`-1`)
expect(a.inclusive).toMatchInlineSnapshot(`true`)
}
{
const a = Number.range(-1, -3, { step: 4, inclusive: function () {} })
expect(a.start).toMatchInlineSnapshot(`-1`)
expect(a.end).toMatchInlineSnapshot(`-3`)
expect(a.step).toMatchInlineSnapshot(`4`)
expect(a.inclusive).toMatchInlineSnapshot(`true`)
}
{
const a = Number.range(0, 5)
expect(() => Object.getOwnPropertyDescriptor(a, "start").call({})).toThrow()
}
expect(that(Iterator.range(0, 0, { step: NaN }))).toMatchInlineSnapshot(`""`)
expect(that(Iterator.range(0, 5, NaN))).toMatchInlineSnapshot(`""`)
})

test("Step infer", () => {
expect(that(Number.range(0, -2))).toMatchInlineSnapshot(`"0f, -1f"`)
expect(that(BigInt.range(0n, -2n))).toMatchInlineSnapshot(`"0n, -1n"`)
expect(that(Number.range(0, -2, { inclusive: true }))).toMatchInlineSnapshot(`"0f, -1f, -2f"`)
expect(that(BigInt.range(0n, -2n, { inclusive: true }))).toMatchInlineSnapshot(`"0n, -1n, -2n"`)
expect(that(Iterator.range(0, -2))).toMatchInlineSnapshot(`"0f, -1f"`)
expect(that(Iterator.range(0n, -2n))).toMatchInlineSnapshot(`"0n, -1n"`)
expect(that(Iterator.range(0, -2, { inclusive: true }))).toMatchInlineSnapshot(`"0f, -1f, -2f"`)
expect(that(Iterator.range(0n, -2n, { inclusive: true }))).toMatchInlineSnapshot(`"0n, -1n, -2n"`)
})

test("Error handling: Type Mismatch", () => {
Expand Down Expand Up @@ -123,43 +95,37 @@ test("Error handling: Type Mismatch", () => {
[0n, 1n, { step: 1 }],
]
for (const each of sharedMatrix) {
expect(() => Number.range(...each)).toThrowError()
expect(() => BigInt.range(...each)).toThrowError()
expect(() => Iterator.range(...each)).toThrowError()
expect(() => Iterator.range(...each)).toThrowError()
}
expect(() => Number.range(0n, 1n)).toThrowError()
expect(() => Number.range(0n, 1n, 1n)).toThrowError()
expect(() => Number.range(0n, 1n, { step: 1n })).toThrowError()
expect(() => BigInt.range(0, 1)).toThrowError()
expect(() => BigInt.range(0n, NaN)).toThrowError()
expect(() => BigInt.range(0, 1, 1)).toThrowError()
expect(() => BigInt.range(0, 1, { step: 1 })).toThrowError()
expect(() => Iterator.range(0n, NaN)).toThrowError()
})

test("Error: Zero as step", () => {
expect(() => Number.range(0, 10, 0)).toThrowError()
expect(() => Number.range(0, 10, { step: 0 })).toThrowError()
expect(() => BigInt.range(0n, 10n, 0n)).toThrowError()
expect(() => BigInt.range(0n, 10n, { step: 0n })).toThrowError()
expect(() => Iterator.range(0, 10, 0)).toThrowError()
expect(() => Iterator.range(0, 10, { step: 0 })).toThrowError()
expect(() => Iterator.range(0n, 10n, 0n)).toThrowError()
expect(() => Iterator.range(0n, 10n, { step: 0n })).toThrowError()
})

test("Error: Infinity as start / step", () => {
expect(() => Number.range(Infinity, 10, 0)).toThrowError()
expect(() => Number.range(-Infinity, 10, 0)).toThrowError()
expect(() => Number.range(0, 10, Infinity)).toThrowError()
expect(() => Number.range(0, 10, { step: Infinity })).toThrowError()
expect(() => Iterator.range(Infinity, 10, 0)).toThrowError()
expect(() => Iterator.range(-Infinity, 10, 0)).toThrowError()
expect(() => Iterator.range(0, 10, Infinity)).toThrowError()
expect(() => Iterator.range(0, 10, { step: Infinity })).toThrowError()
})

test("Incompatible receiver", () => {
function* x() {}
const y = x()
const z = Number.range(0, 8)
const z = Iterator.range(0, 8)
expect(() => z.next.call(y)).toThrow()
expect(() => y.next.call(z)).toThrow()
y.next()
})

test("Inclusive on same start-end (issue #38)", () => {
expect(that(Number.range(0, 0, { inclusive: true }))).toMatchInlineSnapshot(`"0f"`)
expect(that(Iterator.range(0, 0, { inclusive: true }))).toMatchInlineSnapshot(`"0f"`)
})

function that(x) {
Expand Down
11 changes: 11 additions & 0 deletions build-polyfill.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import builder from "core-js-builder"

const bundle = await builder({
modules: [/esnext.+iterator/],
summary: {
console: { size: true, modules: false },
comment: { size: false, modules: true },
},
format: "bundle",
filename: './iterator-helper.js',
})
22 changes: 11 additions & 11 deletions compare.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,16 @@ Based on the document and REPL of other languages, might have error in it.

### Syntax

| Language | Syntax |
| ------------------ | ------------------------------------------------------------------------ |
| This proposal | `Number.range(start, to, step?)` <br /> `Bigint.range(start, to, step?)` |
| Python | `range(start, to, step?)` <br /> `range(to)` |
| Java | `IntStream.range(start, to)` <br /> `LongStream.range(start, to)` |
| Swift (`Range`) | `start...to` <br /> `start..<to` |
| Swift (`StrideTo`) | `stride(from: var_start, to: var_to, by: var_step)` |
| Rust | `(start..to)` <br /> `(start..=to)` |
| Haskell | `[start,next_element_to_infer_step..to]` |
| F# | `seq { start .. step .. to }` |
| Language | Syntax |
| ------------------ | -------------------------------------------------------------------------- |
| This proposal | `Iterator.range(start, to, step?)` <br /> `Bigint.range(start, to, step?)` |
| Python | `range(start, to, step?)` <br /> `range(to)` |
| Java | `IntStream.range(start, to)` <br /> `LongStream.range(start, to)` |
| Swift (`Range`) | `start...to` <br /> `start..<to` |
| Swift (`StrideTo`) | `stride(from: var_start, to: var_to, by: var_step)` |
| Rust | `(start..to)` <br /> `(start..=to)` |
| Haskell | `[start,next_element_to_infer_step..to]` |
| F# | `seq { start .. step .. to }` |

Haskell: The `[start..to]` syntax produce a list. Due to the lazy evaluation of Haskell, it range semantics is different than most of languages.

Expand Down Expand Up @@ -58,7 +58,7 @@ Define:
- This proposal: It doesn't have it own class currently but it have it's own prototype `%RangeIteratorPrototype%` and have unique getters on it.
- Java: The base interface of `IntStream` (`Stream`) doesn't implements `Iterator<T>` protocol but have a `iterator()` methods that returns an Iterator. Must use with `for(int i: range.iterator())`
- Swift (`StrideTo`): According to the [document of `StrideTo`](https://developer.apple.com/documentation/swift/strideto/1689269-lazy), laziness is opt-in.
- Rust: See https://github.com/tc39/proposal-Number.range/issues/17#issuecomment-642064127
- Rust: See <https://github.com/tc39/proposal-Number.range/issues/17#issuecomment-642064127>
- Haskell: No Iterator / Iterable. The laziness is in the language. `The idea of a side-effecting iterator is antithetical to the Haskell Way.` (start StackOverflow)

### Immutable (`start`, `to` and `step` cannot be changed)
Expand Down
26 changes: 8 additions & 18 deletions global.d.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,11 @@
type Infinity = number
interface RangeIterator<T extends number | bigint>
extends Iterator<T, void, void> {
// This property is not in the spec yet.
[Symbol.iterator](): RangeIterator<T>
readonly [Symbol.toStringTag]: "RangeIterator"
readonly start: T
readonly end: T | Infinity
readonly step: T
readonly inclusive: boolean
declare var Iterator: {
new (): Iterator<never, void, void>
prototype: Iterator<never, void, void>
range(start: number, end: number, option: number | NumericRangeOptions<number>): IterableIterator<number>
range(start: bigint, end: bigint | Infinity, option: bigint | NumericRangeOptions<bigint>): IterableIterator<bigint>
}
type RangeFunction<T extends number | bigint> = {
range(
start: T,
end: T | Infinity,
option?: T | { step?: T; inclusive?: boolean }
): RangeIterator<T>
range(start: T, end: T | Infinity, step?: T): RangeIterator<T>
interface NumericRangeOptions<T extends bigint | number> {
step?: number | number
inclusive?: boolean
}
interface NumberConstructor extends RangeFunction<number> {}
interface BigIntConstructor extends RangeFunction<bigint> {}
Loading