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++) { ... }
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)
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)
}
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
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 comoNumber.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;
}
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
}
Ou até mesmo simplificar um pouco fazendo um destructuring da propriedade:
const { range } = Number
console.log(...range(1, 10, 2))
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 comrange(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 o0
, 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']
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]
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) {}
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))
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!