Array generation

# Dmitry A. Soshnikov (14 years ago)

On 10.07.2011 15:54, David Bruant wrote:

Le 10/07/2011 12:06, Dmitry A. Soshnikov a écrit :

(...)

Another thing to consider is Array.prototype.fill method which we discussed before.

The problem:

Array(4).map(function(x) x * x); // [NaN, NaN, NaN, NaN]

(by the way, this mistaken example is still mentioned in this document strawman:shorter_function_syntax, in the "Alternate Syntax Proposals" section, though, is fixed in the main section with let randomArray = NonHoleyArray(10).map(#{Math.random()}); when was mentioned before).

The solution:

// fill with a simple value

Array(4).fill(true); // [true, true, true, true]

// fill with a function in needed context of this

let object = {data: 4}; Array(3).fill(-> if this.data> 3 { 'accepted' } else {'declined' }, object); What is Array(9).fill(function(){})? An array filled with 9 "undefined"? An array filled with 9 (no-op) functions? 9 undefined according to the implementation you provided. I think it should be 9 functions.

It's a good question. Though, usually it's needed to initiate an array just with a simple value, so the case with a function (as a filler) was provided as just an addition. From this viewpoint, yes, perhaps it should 9 functions.

Another syntax should be provided to generate values with a function. Maybe: Array.generate(count, fillerFunction/throws a TypeError if not callable/, thisArg) (call "count" times fillerFunction.bind(thisArg) and return value of i-th call is [[Put]] as i-th element of the array) Array.fill could be implemented as: Array.fill = function(c, v){ return Array.generate(c, (-> v) ); };

Yes, it's common way, though, that's said, usually it's needed only to fill an array with e.g. Array.fill(1)

So from this viewpoint (and regarding that example with squares), it's good to have also Array.seq(from, to) method (the name is taken from Erlang, I just frequently uses lists:seq(from, to) there):

Array.seq(1, 5).map((x) -> x * x); [1, 4, 9, 16, 25]

(...)

Also class-method Array.fill(count, filler) can be considered. Since .fill seems to be intended for initialization, I would tend to be in favor of an Array.fill method rather than Array.prototype.fill. But a Array.fill syntax would prevent from choosing the prototype.

Maybe harmony:array_comprehensions could be enhanced to support array generation? Since I'm not familiar with the proposal, I won't try to play with the grammar, but it sounds like the right place to suggest initialization syntax. And it would solve the prototype issue in combination with the proto operator: myProto<| [/my syntax generating an array of 1000 elements/]

Yes, also have to consider it and think.

Dmitry.

# David Herman (14 years ago)

So from this viewpoint (and regarding that example with squares), it's good to have also Array.seq(from, to) method (the name is taken from Erlang, I just frequently uses lists:seq(from, to) there):

<bikeshed>Array.range seems like an intuitive name as well.</bikeshed>

Array.seq(1, 5).map((x) -> x * x); [1, 4, 9, 16, 25]

This pattern (integer range immediately followed by map) is so common that many Schemes have a more general function that fuses the two traversals, sometimes called build-list or list-tabulate:

Array.build(n, f) ~~~ [f(0), ..., f(n-1)]

Another common and useful fusion of two traversals that's in many Schemes is map-filter or filter-map:

a.filterMap(f) ~~~ [res for [i,x] of items(a) let (res = f(x, i)) if (res !== void 0)]

I rather arbitrarily chose to accept both null and undefined here as way to say "no element" -- a reasonable alternative would be to accept only undefined as "no element".

# Rick Waldron (14 years ago)

David, I like the way you paint your bike sheds. Array.range() (or similarly functional but differently named) is definitely another one of those "oft-rerolled" solutions.

# David Herman (14 years ago)

a.filterMap(f) ~~~ [res for [i,x] of items(a) let (res = f(x, i)) if (res !== void 0)]

I rather arbitrarily chose to accept both null and undefined here as way to say "no element" -- a reasonable alternative would be to accept only undefined as "no element".

Oops, I meant to say, I rather arbitrarily chose to accept only undefined and a reasonable alternative would be to accept null or undefined, i.e.:

a.filterMap(f) ~~~ [res for [i,x] of items(a) let (res = f(x, i)) if (res != null)]

I think I prefer the only-undefined version.

# liorean (14 years ago)

On 10 July 2011 22:23, David Herman <dherman at mozilla.com> wrote:

Another common and useful fusion of two traversals that's in many Schemes is map-filter or filter-map:

a.filterMap(f) ~~~ [res for [i,x] of items(a) let (res = f(x, i)) if (res !== void 0)]

I rather arbitrarily chose to accept both null and undefined here as way to say "no element" -- a reasonable alternative would be to accept only undefined as "no element".

The way I think of it is that in analogy to NaN being the Numbers that represent no number, null is the Object that represents no object, in other words a reasonable value to store to tell just that. The undefined value is by analogy the value that represents no value, so is the only value that should be a "no element".

But that might be just my way of thinking about and distinguishing the not-a-something special cases.

# David Herman (14 years ago)

Agreed. I think that's a pretty common way people think about null vs undefined, and it's consistent with the language's behavior.

# Brendan Eich (14 years ago)

range.doc 'range([start,] stop[, step]) -> range object\n\nReturns a virtual sequence of numbers from start to stop by step.'

[i for i in range(10)] [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

[i for i in range(10, 20)] [10, 11, 12, 13, 14, 15, 16, 17, 18, 19]

[i for i in range(10, 20, 2)] [10, 12, 14, 16, 18]

It seems to me we want

  • range(end), range(start, end), range(start, end, step) as in Python 3, a generator.

    function range(...args) { const start = (args.length == 1) ? 0 : Number.toInteger(args[0]); const end = Number.toInteger((args.length > 1) ? args[1] : args[0]); const step = (args.length > 2) ? Number.toInteger(args[2]) : 1;

    if (Number.isNaN(start) || Number.isNaN(end) || Number.isNaN(step)) { throw new TypeError("..."); }

    for (let i = start; i != end; i += step) { yield i; } }

  • a way to take an iterator I and exhaust it to build an array: [x for x of I] is the comprehension form (note the paren-free for-of head). A functional form per Allen's principle would be helpful some of the time: Array.fromIterator or Array.exhaust?

  • a standard array element-key-only-and-as-integral-index (not string) and elements-only iterators: for i of indexes(array), for v of elements(array).

See harmony:iterators for more on for-of.

# Andreas Rossberg (14 years ago)

On 10 July 2011 22:23, David Herman <dherman at mozilla.com> wrote:

Another common and useful fusion of two traversals that's in many Schemes is map-filter or filter-map:

a.filterMap(f) ~~~ [res for [i,x] of items(a) let (res = f(x, i)) if (res !== void 0)]

I rather arbitrarily chose to accept both null and undefined here as way to say "no element" -- a reasonable alternative would be to accept only undefined as "no element".

\bikeshed{ The SML lib calls this one mapPartial, which I think is a much better name. }

# Bill Frantz (14 years ago)

On 7/11/11 at 15:09, liorean at gmail.com (liorean) wrote:

On 10 July 2011 22:23, David Herman <dherman at mozilla.com> wrote:

Another common and useful fusion of two traversals that's in many Schemes is map-filter or filter-map:

a.filterMap(f) ~~~ [res for [i,x] of items(a) let (res = f(x, i)) if (res !== void 0)]

I rather arbitrarily chose to accept both null and undefined here as way to say "no element" -- a reasonable alternative would be to accept only undefined as "no element".

The way I think of it is that in analogy to NaN being the Numbers that represent no number, null is the Object that represents no object, in other words a reasonable value to store to tell just that. The undefined value is by analogy the value that represents no value, so is the only value that should be a "no element".

The way I think of it is that NaN can be produced if an algorithm "blows up" numerically. If that happens, and the result gets stored in the array, I don't want to confuse those values with the "placeholder for purposely omitted value" value.

Cheers - Bill


Bill Frantz |"Web security is like medicine - trying to do good for 408-356-8506 |an evolved body of kludges" - Mark Miller www.periwinkle.com |