Proposal: Array.prototype.first() and Array.prototype.last()

# Nicu Micleusanu (8 years ago)

I propose to standardize Array.prototype.first() and Array.prototype.last(), very similar to underscore _.first() and _.last().

A very basic implementation:


Array.prototype.first = function (n) {

     if (!arguments.length) {

         return this[0];

     } else {

         return this.slice(0, Math.max(0, n));

     }

};


Array.prototype.last = function (n) {

     if (!arguments.length) {

         return this[this.length - 1];

     } else {

         return this.slice(Math.max(0, this.length - n));

     }

};

# Bob Myers (8 years ago)

This is well-traveled territory.

Whatever is or is not implemented, interfaces which have optional arguments and return scalars in one case and arrays in another case are a horrible idea.

To my knowledge no-one has ever explained why the following is a bad idea:

array.0
array.-1

Bob

# Nicu Micleusanu (8 years ago)

Well, String.prototype.match() does this, it returns an array of matches or null in case there are no matches. I agree that in some situation this can be a source of errors, but the implementation would be quite useful.

In case it's not acceptable, I would propose read-only members Array.prototype.first and Array.prototype.last:

Object.defineProperties(Array.prototype, {
     first: {
         get: function () {
             return this[0];
         }
     },
     last: {
         get: function () {
             if (this.length) {
                 return this[this.length - 1];
             } else {
                 return void 0;
             }
         }
     }
});
# Li Xiaolong (8 years ago)

It is obvious that ‘.’ before a number will be recognized as a decimal point, which is different from the point that gets a property. For example, .2===0.2 is true. What’s more, the array’s prototype is object, which means arr[-1] will get the value of -1 rather than length-1. The array can store values at -1 like other objects. If you use -1 to represent length-1, so how do we refer to the value of -1?

# Bob Myers (8 years ago)

If you use a.-1 to represent a[length-1], so how do we refer to the value of the -1 property?

a[-1], as at present.

It is obvious that ‘.’ before a number will be recognized as a decimal point,

I don't think it's that obvious. I'm not a parsing guy, but I don't think the parser works backward, looking for dots preceding numbers. I would assume simplistically that a dot following an expression currently puts the parser into a mode where it is looking for the following identifier, so that logic could conceivably be tweaked to look alternatively for an integer index. a.0 seems eminently parseable to me.

# Li Xiaolong (8 years ago)

Reply to Bob Myers:

Maybe you are right. It's just most programmers are not used to variables or properties that starts with a number.

# Andrea Giammarchi (8 years ago)

I don't think I've ever seen an arr[-1] access that wasn't meant to grab last value same as arr.slice(-1) would do.

If you deal with arrays and you are treating them as generic objects and you store numeric values like -1 I'm not sure why would you do that or what's your use case, all I know is that this will never be solved in TC39 so you can use a module and live happily ever after (really).

www.npmjs.com/package/length-1

Reason for that:

  • it's the most common use case
  • nobody ever had issues with its implementation

Best

# Li Xiaolong (8 years ago)

Reply to Bob Myers:

I don't think it's that obvious. I'm not a parsing guy, but I don't think the parser works backward, looking for dots preceding numbers. I would assume simplistically that a dot following an expression currently puts the parser into a mode where it is looking for the following identifier, so that logic could conceivably be tweaked to look alternatively for an integer index. a.0 seems eminently parseable to me.

It came to me that an expression is divided by the operator from lowest cn.bing.com/dict/search?q=priority&FORM=BDVSP6&mkt=zh-cn priority

to highest cn.bing.com/dict/search?q=priority&FORM=BDVSP6&mkt=zh-cn priority.

So, the 'a.-1' will first be splited into 'a.', '-', and '1'. Then the 'a.' is not legal. The priority is not to be changed because it will impact many things.

# Jeff Walden (8 years ago)

On 09/27/2016 05:38 AM, Bob Myers wrote:

To my knowledge no-one has ever explained why the following is a bad idea:

array.0
array.-1

Consider this already-valid code:

var first = array .0.toString();

This parses right now as

var first = array; (0.0).toString();

So your proposal would break existing code. We could imagine inserting a [no LineTerminator here] inside MemberExpression to permit "." NumericLiteral and "." "-" NumericLiteral to appear here, to be sure. But that's extra complexity, extra ASI-handling (having worked on ASI handling recently, I assure you there's nothing simple about it, and further complicating ASI is a strong demerit in my book), all for IMO dubious value.

# 段垚 (8 years ago)

Because foo.bar is equivlant to foo['bar'] in JS so far, and array.-1 could break this consistency.

On the other hand, array.first() seems not necessary because array[0] is even more handy; array.last() looks fine to me.

If someone prefer a more general solution, I recommand array.get(n):

  • if n >= 0 && n < array.length: equivlant to array[n]
  • if n < 0 && -n < array.length: equivlant to array[array.length + n]
  • if n <= -array.length || n >= array.length: throw or return undefined
  • if n is not a integer or not a number: throw or return undefined

The last 2 rules make array.get(n) less error prone than array[n]. I prefer throwing, but maybe returning undefined is more JS-style?

在 2016/9/27 20:38, Bob Myers 写道:

# Claude Pache (8 years ago)

Le 28 sept. 2016 à 07:38, 段垚 <duanyao at ustc.edu> a écrit :

Because foo.bar is equivlant to foo['bar'] in JS so far, and array.-1 could break this consistency.

On the other hand, array.first() seems not necessary because array[0] is even more handy; array.last() looks fine to me.

If someone prefer a more general solution, I recommand array.get(n):

  • if n >= 0 && n < array.length: equivlant to array[n]
  • if n < 0 && -n < array.length: equivlant to array[array.length + n]
  • if n <= -array.length || n >= array.length: throw or return undefined
  • if n is not a integer or not a number: throw or return undefined

The last 2 rules make array.get(n) less error prone than array[n]. I prefer throwing, but maybe returning undefined is more JS-style?

For consistency with the rest of the builtin library, array.get(n) should be equivalent to array.slice(n)[0], which means: convert n to an integer, and: return undefined for out-of-bound index.

# 段垚 (8 years ago)

在 2016/9/28 14:42, Claude Pache 写道:

Le 28 sept. 2016 à 07:38, 段垚 <duanyao at ustc.edu <mailto:duanyao at ustc.edu>> a écrit :

Because foo.bar is equivlant to foo['bar'] in JS so far, and array.-1 could break this consistency.

On the other hand, array.first() seems not necessary because array[0] is even more handy; array.last() looks fine to me.

If someone prefer a more general solution, I recommand array.get(n):

  • if n >= 0 && n < array.length: equivlant to array[n]
  • if n < 0 && -n < array.length: equivlant to array[array.length + n]
  • if n <= -array.length || n >= array.length: throw or return undefined
  • if n is not a integer or not a number: throw or return undefined

The last 2 rules make array.get(n) less error prone than array[n]. I prefer throwing, but maybe returning undefined is more JS-style?

For consistency with the rest of the builtin library, array.get(n) should be equivalent to array.slice(n)[0], which means: convert n to an integer, and: return undefined for out-of-bound index.

I regard such converting behavior a bad legacy of JS, and want to avoid it in new APIs.