Yeah, thinking about how we would go about putting together a proposal for this, it seems hard to create a case for adding a single function that's just a shorthand for your code snippet.
I mean, I would be happy if such a function existed, but I also wouldn't be disappointed if all we had was Number.range() and the iterator proposal. As long as there's something to replace the need for Array.from({ length: ... }).
I would add discoverable to that list. And Number.range, I would say, does not fall into that category. If you're looking to create an array and you're not familiar with the language (never mind range and iterator helpers being new), you'll look to the Array APIs to see what's available to you. It's hard to see what there will help you with what you're looking for.
Looking back, I think one of the more common problems I've seen with new users and array creation like this is with fill(). People see they can create an array of a specific length with the Array function/constructor, then see they can fill it with values using fill(). That leads them to a path of something like:
new Array(5).fill({})
thinking it will give them an array with 5 unique objects in it. If/when they realize it's the same object in each index, they have to figure out how to make them unique which is a bit of a challenge.
Because of this, I'm thinking maybe we need something more along the lines of a... fillEach() method that will iterate over an array's length (not values) and fill the array with the return values of a supplied callback.
Anything that encourages ANY use of new Array() is bad and would be harmful to the language. Additionally, I don't want to see any more array mutator methods added, ever.
Perhaps it's simply the fact that most programmers aren't conscientiously designing their APIs to support them, because they're rarely useful and often forgotten. Maybe it wouldn't be such a bad thing if it were a completely separate data type.
Dunno, it's interesting learning about what TC39 members discourage as I participate in these forms. These sorts of opinions usually don't end up being reflected on doc sites like MDN.
new Array() and Array() are to be avoided because they make sparse arrays.
Sparse arrays are to be avoided for a number of reasons - one is because conceptually, an array is an atomic list of items, and "holes" break that mental model. Another is that a hole causes an index lookup on the array to look up the index on Array.prototype. Another is that sparse arrays (possibly because of the prototype lookup) have prohibitively slow performance in most engines.
Sparse arrays are icky, and shouldn't exist, and all modern Array methods pretend they don't.
Can we do number.times(fn), i.e. put the method into Number.prototype? This significantly reduces characters while maintaining descriptiveness (the method name can be discussed). You may argue that returning an array in Number.prototype sounds weird though.
I kinda like the Array.fromLength. Makes it abundantly clear what idiom it's replacing.
It's not just the fake arrayLike argument, it's also the clunky mapping function receiving useless first argument, and also all the lookups into the fake arrayLike that fromLength would avoid.
I like fromLength() (or whatever it ends up being called). It creates a path through the thickets.
But if we have fromLength() what should the second argument be? Should it take a plain value and you map it, if needed? (Easier to explain to novices.) Or would that create the Array.fromLength( 4, {} ) bear trap?
Crazy, far-out, silly, fugly brainstorming: if the "plain value" is a class, could new be implied? i.e. class MyClass { foo = 5; }; Array.fromLength(4, MyClass) as sugar for Array.from( {length: 4}, () => new MyClass)
If it takes a regular mapping function, should that get the index? (Personal opinion: yes, Array.fromLength(4, index=>index) is useful. But I could live without it, I guess.) Does fromLength() need a third thisArg argument, as happens for from()?
Should it be replicated on typed arrays? If it was, then there would be a single consistent creation method across all array-likes. Currently new Array(256) is unsafe whereas new Uint8Array(256) is safe. (And having a consistent method across all Array likes seems like another argument for it's existence.)
If it's taking a mapping function, what about adding it to Sets and Maps? const powersOfTen = Set.fromLength( 16, exp => 10 ** exp)
I'm pushing the envelope here so as to make the base case look moderate and reasonable and an entirely justifiable addition to the language. ;)
const range = Number.range(1, 11)
range.generate(Array, v => v ** 2)
range.generate(Set, v => v ** 2)
range.generate(Map, v => [ v.toString(16), v ** 2 ])
Something like Symbol.generateCollection() can be used behind this generate method.
A more general approach would be a generic map(iterable, fn) function, not another single-purpose method on Number.range that requires extra symbol to even work.
Array.from(Function.map(range, v => v ** 2));
new Set(Function.map(range, v => v ** 2));
new Map(Function.map(range, v => [ v.toString(16), v ** 2]));
alternatively it could be a method on Function.prototype:
Array.from((v => v ** 2).generate(range));
new Set((v => v ** 2).generate(range));
new Map((v => [ v.toString(16), v ** 2]).generate(range));
It not only fits the original goal but also has richer features. Since there is still no API with similar functionality, it will be highly more convincing than what we have discussed.
Creating multidimensional arrays from ranges/arrays is an intriguing idea.
But the api you proposed is not clear to me ;(
the argument i has to always begin with zero? what if we want a step? Can you explain the mapping you're introducing here?
Canβt you just do i * step? i and j are both indices of the array in the dimensions.
More formally, For a non-negative integer n in the pseudo-expression [x_0, x_1, β¦, x_(n-1)].buildShape((y_0, y_1, β¦, y_(n-1)) => β¦β¦), y_n is a non-negative integer smaller than (and not equal to) x_n representing its index in the (n+1)th dimension.
So [5].buildShape(i => i * 2) should totally work like Array.from({ length: 5 }, (_, i) => i * 2).