Skip to content

johnhenry/make-numbers-iterable

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

13 Commits
 
 
 
 

Repository files navigation

Numbers as Iterarors

ECMAScript proposal, specs, tests, and reference implementation for Numbers as iterators.

This initial proposal was drafted by @johnhenry with input from @_ and @_.

Designated TC39 reviewers: @_ @_

Table of Contents

Status

This proposal is currently at stage 0 of the process.

Rationale and Motivation

There are a few ways to loop through a ordered list of integers, 0 through n-1 in JavaScript.

Using a for loop:

//for syntax
for(let i=0; i < n; i++){
    //do something with i
}

Using a while loop:

//while syntax
let i = 0;
while(i < n){
  //do something with i
  i++;
}

Both syntaxes are awkward syntax for a number of reasons:

  • they require an explicit, non-constant, declaration of a placeholder,

    let i = 0
  • they requires an explicit test for said placeholder

    i < n
  • they require explicit incrementation step of said placeholder

    i++

Prior Art

Ruby

In Ruby, this can be achieved more elegantly using a number's times method:

#number#times syntax
n.times do |i|
  #do something with i
end

This can also be achieved with Ranges.

#range#each syntax
(0..n).each do |i|
  #do something with i
end
#for-in syntax
for i in 0..n
  #do something with i
end

Python

Python also achieves this with the concept of Ranges.

#for-in syntax
for i in range(n):
  #do something with i

Coffeescript

Coffeescript also achieves this with the concept of Comprehensions over Ranges.

#for-in syntax
(#do something with i
  for i in [0..n-1])

Proposed Solution: Make Numbers Iterable

We can do away with most of the awkwardness using the iterator protocol to make numbers iterable.

Given a positive number n, invoking n[Symbol.iterator] will return an iterator yielding the sequence [0, n) or 0 up to n - 1, inclusive. When n is negative, the iterator instead yields the sequence [n, 0) or n up to -1, inclusive. This definition for negative numbers is useful as it allows a method of iterating between two arbitrary integers. (See the FAQ for more details.)

for-of loop

An iterable number can be the target of a for-of loop, mirroring the for-in syntax from other languages:

//for-of syntax
for(const i of n){
  //do something with i
}

conversion to array

Iterable Numbers can be turned into arrays and use their methods, mimicking Ruby's range#each syntax and allowing other operations.

[...n].forEach(i=>{
  //do something with i
});
[...n].map(i=>{
  //do something with i
});
[...n].filter(i=>{
  //do something with i
});
[...n].reduce((_,i)=>{
  //do something with i
});

Array.from works similarly for Iterable Numbers.

Array.from(n);//[0, 1, ... n-1]

conversion to set

Like any iterator, iterable Numbers can be converted into sets.

new Set(n);//Set {0,1,...n-1}

set theory

On a side note, this fits well with the set theory of integers.

Compare the definition of von Neumann ordinals

n := {x in N | x < n}
0 := {}
1 := {0}
2 := {0, 1}
3 := {0, 1, 2}
...

to results here.

const zero  = new Set(0);//Set {}
const one   = new Set(1);//Set {0}
const two   = new Set(2);//Set {0, 1}
const three = new Set(3);//Set {0, 1, 2}
//...

variadic functions

Iterable Numbers can be spread across variadic functions.

const sum = (...numbers)=>numbers.reduce((a, b)=>a+b, 0);
sum(...n);//0 + 1 + ... n-2 + n-1 === (n(n-1))/2

infinite loops

Using Infinity as an iterator provides a convenient method for potentially infinite loops.

Compare what we might have done before:

let i = 0;
while(true){
  //do something with i
  if(/*some condition*/){
    break;
  }
  i++;
}

to what we would do now

for(const i of Infinity){
  //do something with i
  if(/*some condition*/){
    break;
  }
}

destructuring

Iterable numbers can be destructured as arrays.

const [,one,,three] = Infinity;//
one;//1
three;//3

negative numbers

Iterable negative numbers yield the sequence from -n up to -1 instead of 0 up to n-1.

[... -n];//[-n, -n+1... -1]

combinations

Iterable numbers can be combined with other iterable objects.

[...-n, ...[0,0], ...n+1];//[-n, -n+1... -1, 0, 0, 0, 1 ... n-1, n]

Specification: Number.prototype[Symbol.iterator]

When Number.prototype[Symbol.iterator] is called, the following steps are taken:

  • let N be Math.floor(this value)
  • if N is 0, return a generator in a "done" state.
  • if N is Positive, return a generator that yields numbers 0 to N-1
  • if N is Negative, return a generator that yields numbers N to -1

Implementation

The following code modifies the Number object's prototype, allowing any positive number, n, to be used as an iterator, yielding 0 up to n-1. (technically, the floor of n-1). Negative numbers return a generator yielding n up to -1.

Paste

You can paste the following code into a console:

Object.defineProperty(
Number.prototype,
Symbol.iterator,
{ value: function *(){
    const max = Math.max(0, Math.floor(this));
    let i = Math.min(0, this);
    while(i < max) {
      yield i;
      i+=1;
    }
    return this;
  }
});

and then try out the above examples to see this proposal in action.

Import

You can import the included "make-numbers-iterable.js" into a project.

import './make-numbers-iterable';
//...

and then try out the above examples to see this proposal in action.

Alternate Solution: Add Static Method to Number Object

Alternatively, we might add an "interator" method to the Number object.

for (const n of Number.iterator(number)){
  //do something with n
}

This can be implemented with the following code.

Object.defineProperty(
Number,
"iterator",
{ value: function *(num){
    const max = Math.max(0, Math.floor(num));
    let i = Math.min(0, num);
    while(i < max) {
      yield i;
      i+=1;
    }
    return num;
  }
});

FAQ

How does one interate between arbitrary integers?

Iterate up to the difference and add the smaller to each the iteration

for(const i of (larger - smaller)){
  //do something with (smaller + i)
}

This will produce the sequence:

[smaller, smaller + 1, ..., larger - 1]

Because of our definition for negative integers, it doesn't matter which numbers is smaller, as long as the subtrahend is added to the iteration. The following code produces the same sequence as the code above:

for(const i of (smaller - larger)){
  //do something with (larger + i )
}

Does this cause language conflicts?

Currently if a number is given where an iterator is expected, a type error is thrown:

TypeError: (var)[Symbol.iterator] is not a function()

As such, this would not cause conflicts with existing working code.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published