O que é a nova propostas de sequências com o Iterator.range no JavaScript

Lucas Santos - Mar 13 '23 - - Dev Community

As vezes as propostas mais simples são as que trazem mais valor para algum projeto, e com essa não foi diferente.

Várias linguagens, uma delas, por exemplo, o Python, possuem estruturas para representar e até mesmo criar sequencias de números naturais de uma forma simples. O JavaScript ainda não tinha essa capacidade, e ela foi extensivamente requisitada em diversos projetos.

Mas a espera acabou, hoje a gente vai falar da proposta que vai adicionar o método range a um iterador. Essa é uma proposta que está no início (não passou do estágio 1), mas é extremamente promissora!

Se você não sabe como funciona o JavaScript, nesse vídeo eu explico um pouco mais sobre o processo de lançamento de novas funcionalidades do JavaScript, se você ainda não assistiu, eu recomendo fortemente para poder entender melhor como tudo funciona!

Ranges

Ranges ou sequências são meta-estruturas de uma linguagem, ou seja, elas existem em teoria, mas essencialmente são só um encapsulamento de uma lógica mais simples transformada em um construto de uma linguagem. Ranges são simplesmente uma sequência de números, com um valor mínimo, um valor máximo e um valor opcional chamado step que é a quantidade de números que vamos pular de cada vez.

Ranges são úteis para uma variedade de coisas, mas um dos principais usos é uma alternativa a um iterador, ao invés de fazer algo como:

for (let i = 0; i < 10; i++) { ... }
Enter fullscreen mode Exit fullscreen mode

Você poderia fazer uma chamada mais simples. Por exemplo, no Python, você pode gerar um range de números que vai de 0 a 9 usando esse código:

for n in range(10):
    print(n)
Enter fullscreen mode Exit fullscreen mode

Esse código vai printar números indo de 0 até 9 no console, mas você pode fazer a mesma coisa usando um for simples e contando de 0 até 9, por exemplo, no JavaScript seria algo assim:

let x = [0]
for (let i = 0; i <= 9; i++) {
    x.push(i+1)
}

Enter fullscreen mode Exit fullscreen mode

São mais passos e mais código, e é justamente para isso que essa proposta chegou. Inclusive, ela não tem nenhum objetivo especial de melhorar performance, ou até mesmo de deixar a contagem mais eficiente, é pura e simplesmente pelo fato de que todas as outras linguagens também tem, e uma linguagem sem um range parece incompleta.

Esse é um daqueles tipos de funções que é super simples e tem alguns usos específicos que você não tem como escapar, da mesma forma que a intersecção de dois arrays, ou a diferença entre eles. É simples demais para não estar embutido na linguagem.

Na verdade, existe toda uma questão do Stack Overflow que tem mais de 20 implementações diferentes de um range. A que eu mais gosto é uma implementação simples e contida que usa as chaves de um array como valores:

[...Array(10).keys()]
// 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
Enter fullscreen mode Exit fullscreen mode

Eu inclusive fiz um post háum tempo mostrando como a gente pode implementar iteradores para criar uma função de range, que é exatamente como essa proposta é construída.

Iterator.range

A ideia de como a proposta será implementada ainda está sob discussão, as duas opções seriam:

  • Implementar o método range dentro da classe Number, ficando algo como Number.range
  • Criar uma nova classe chamada Interval que conteria outros tipos de métodos além do range

Essa é uma discussão aberta que você, inclusive, pode participar!

Eu, pessoalmente, não pendo muito para nenhum dos lados, mas dependendo do uso que os ranges devem ter eu diria que sou mais a favor do método do que da classe, pelo menos por agora.

Imaginando que a proposta original, que é utilizar um método na classe Number, esteja ganhando, a proposta vai adicionar uma função range que tem essa assinatura tanto em Number quanto em BigInt:

class Number {
  range(start: number, end: number, options: { step: number = 1, inclusive: boolean = false } | step: number = 1): RangeIterator;
}

class BigInt {
  range(start: bigint, end: bigint | Infinity | -Infinity, options: { step: bigint = 1n, inclusive: boolean = false } | step: bigint = 1n): RangeIterator;
}
Enter fullscreen mode Exit fullscreen mode

Isso significa que poderemos fazer algo desse tipo:

for (const i of Number.range(1, 10)) {
  console.log(i); // => 1, 2, 3, 4, 5, 6, 7, 8, 9
}
Enter fullscreen mode Exit fullscreen mode

Ou até mesmo simplificar um pouco fazendo um destructuring da propriedade:

const { range } = Number
console.log(...range(1, 10, 2))
Enter fullscreen mode Exit fullscreen mode

Onde o terceiro parâmetro pode ser tanto o step quanto um objeto de opções que contém essas propriedades:

  • step: Quantos números devemos pular por vez, esse pode ser um número negativo, portanto você pode criar ranges que começam, por exemplo, do 100 e vão até 0 com range(100, 0, -1)
  • inclusive: é uma propriedade que define se o valor final está incluso na contagem, por exemplo, range(100, 0, -1) contaria de 100 até 1, excluindo o 0, já range(100, 0, {inclusive: true, step: -1}) iria contar de 100 até 0.

Futuras implementações

Essa proposta funciona muito bem com outra proposta chamada slice notation, que adiciona uma notação do tipo [inicio:fim] para o JavaScript. Na proposta um dos exemplos principais é lidar com arrays, por exemplo:

const arr = ['a', 'b', 'c', 'd'];

arr[1:3];
// → ['b', 'c']

arr.slice(1, 3);
// → ['b', 'c']
Enter fullscreen mode Exit fullscreen mode

Ou seja, a ideia é substituir o uso do slice direto, porém, no caso dos ranges, poderíamos fazer algo assim:

const x = 1:3 // [1,2,3]
Enter fullscreen mode Exit fullscreen mode

Que seria o mesmo que usar range(1, 3, { inclusive:true }). E essa parece ser uma proposta bastante interessante, principalmente se pudermos fazer algo assim:

for (let x in 1:100) {}
Enter fullscreen mode Exit fullscreen mode

Conclusão

Mesmo que a proposta seja recente e esteja em discussão, eu acredito que ela vai ser o suficiente para mudar a forma como a gente trabalha com JavaScript, pelo menos de uma forma sutil.

Se você quiser testar essa proposta por ai, o pacote core-js contém polyfills para essas funções e basta você dar um npm install core-js no seu pacote, criando um arquivo como esse index.mjs:

import 'core-js/proposals/number-range.js'
const { range } = Number
console.log(...range(100, 0, -1))

Enter fullscreen mode Exit fullscreen mode

Eu fortemente recomendo fazer seus testes e encontrar outros usos para o range. E também te convido a comentar lá na proposta com suas opiniões sobre como essa API deveria ser!

Curtiu essa ideia? Deixa um comentário aqui com seus usos do range e me chama nas minhas redes sociais que eu vou adorar falar sobre isso!

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .