A proposal for ECMAScript to add a built-in Number.range()
Branch: master
Clone or download
Latest commit ccf1031 Jan 21, 2019
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
README.md Fix typo Jan 22, 2019
global.d.ts Init proposal Jan 18, 2019
polyfill.js Init proposal Jan 18, 2019
tsconfig.json Init proposal Jan 18, 2019

README.md

Number.range

Champions: Finding one...

Author: Jack Works

Stage: N/A

This proposal describes adding a Number.range to JavaScript.

Scope

The goal of this proposal is to add a built-in range function for iterating or other use cases.

range is a very useful function. For example in Python, we can just write

for i in range(5):
    // ....

but we can't do this in JavaScript. There are tons of npm packages implements a range function, tons of threads talking about ranges even in the es-discuss mail list.

So there is no reason we shouldn't have a built-in implementation.

Goals

  • Arithmetic Sequence
    • Incremental (0, 1, 2, 3, ...)
    • Decremental (0, -1, -2, -3, ...)
    • Step (0, 2, 4, 6, ...)
      • Decimal step (0, 0.2, 0.4, ...)
  • BigInt Support
    • Same as Arithmetic Sequence
  • Infinite Sequence Number.range(0, Infinity) -> (0, 1, 2, 3, ...)

Non-goals

  • String Sequence (a, b, c, d, ...)
  • Magic
    • E.g. if (x in Number.range(0, 10))

Discussions

  • Do we need customizable behavior? Something like Number.range(0, 1000, (previous, index) => next)
  • Should we add a new syntax like 2...3 instead of a Number.range()?
  • Should we support Number.range(x) as an alias of Number.range(0, x)?
  • How to deal with bad inputs?
    • Type mismatch Number.range(0, 5n)
    • Direction mismatch Number.range(0, 10, -5)
    • NaN
    • How do we calculate numbers? (The 0.30000000000000004 problem)

Examples

for (const i of Number.range(0, 43)) {
    console.log(i) // 0 to 42
}

const fakeData = [...Number.range(0, 21)].map(x => x ** 2)

function* odd() {
    for (const i of Number.range(0, Infinity)) if (i % 2 === 0) yield i
}

Proposal

Number.range( from , to, step )

Feature assumptions of content below (Wait for discussing)

  • Number.range(to) equals Number.range(0, to)
    • [a] No
    • [b] Yes
  • Handle with direction mismatch
    • [c] throws an Error
    • [d] Ignore the symbol of step, infer from from and to
    • [e] Respect direction mismatch (and cause a dead loop)

Signature

interface NumberConstructor {
    range(from: number, to: number, step?: number): Iterator<number>
    range(from: BigInt, to: BigInt, step?: BigInt): Iterator<BigInt>
    // If accept Number.range(to)
    range(to: number): Iterator<number>
    range(to: BigInt): Iterator<BigInt>
}

Context

Number.range is a generator.

Input check

Check the input type.

  1. If Type(from) is not number or BigInt, throw a TypeError exception.

a. Does not accept Number.range(to)

  1. Do nothing

b. Accept Number.range(to)

  1. If Type(to) is undefined, let to = from, from = 0 or 0n

Goes on...

  1. If Type(to) is not number or BigInt, throw a TypeError exception.
  2. If Type(step) is not number, undefined or BigInt, throw a TypeError exception.

Check if input shares the same type.

  1. If Type(from) is not equal to Type(to), throw a TypeError exception.
  2. If Type(step) is not undefined, and Type(from) is not equal to Type(step), throw a TypeError exception.

Quit early with NaN.

  1. If from is NaN, return undefined.
  2. If to is NaN, return undefined.
  3. If step is NaN, return undefined.

Set undefined step to 1 or 1n

  1. If Type(step) is undefined, let step = 1 or 1n

Handle with direction mismatch

  1. If step is 0 or 0n, throws an exception.
  2. let ifIncrease = to > from

c. Throws an exception

  1. let ifStepIncrease = step > 0
  2. if ifIncrease is not equal to ifStepIncrease, throws a RangeError exception.

d. Ignore the symbol of step, infer from from and to

  1. If ifIncrease is true, let step = abs(step)
  2. If ifIncrease is false, let step = -abs(step)

e. Respect direction mismatch (and cause a dead loop)

  1. Do nothing
  2. Do nothing

Yield Numbers! (Not written in spec language yet)

These two implementations may act differently due to IEEE 754 floating point number

Implementation 1

  1. Run the code below.
while (ifIncrease ? !(from >= to) : !(to >= from)) {
    yield from
    from = from + step
}

Implementation 2

  1. Run the code below.
let count = typeof from === 'bigint' ? 1n : 1
let now = from
while (ifIncrease ? !(now >= to) : !(to >= now)) {
    yield now
    now = from + step * count
    count++
}

Over

  1. return undefined

Polyfill

Here is a polyfill.