proposal-array-last icon indicating copy to clipboard operation
proposal-array-last copied to clipboard

Negative indexes with array.get(-1) and array.set(-1, value)

Open Zarel opened this issue 5 years ago • 6 comments

.get(-1) and .set(-1, value) have gotten a lot of support in #5, but since it's a separate proposal (and hasn't gotten a response from @keithamus yet), I figure it probably deserves its own issue.

Summary of reasons to favor it:

  • they allow access to arbitrary distances from the end, not just the last value
  • they're consistent with Map
  • they don't use getters/setters, which are a bit too magical for my tastes (setters especially – setting a property shouldn't have any side effect other than setting that property)
  • they can be polyfilled in ES3 engines

@rauschma mentioned "I remember there were arguments against those methods, but forgot what they were", but I've yet to find any arguments against this idea.

Looking at the comments in #5, I count 17 people in support (3 comments and 14 other thumbs-ups) and no opposition.

Zarel avatar Jan 31 '19 13:01 Zarel

It's a good idea, I think to have something like in Python where you could write arr[:-1] or something that would be better and shorter.

Berkmann18 avatar Jan 31 '19 13:01 Berkmann18

Would set follow in the steed of Map and WeakMap with regards to returning the array object instead of the value... In retrospect some regard this as an over-site that with enough will, would wish to see improved with the addition a put method.

That is array.set(-1, value) could either return array or value. The former is what Weak(Map) do.

I do like set/get much more than any single non-indexable getter/setter pair.

thysultan avatar Jan 31 '19 15:01 thysultan

It could also return undefined; but I’d find array most useful and consistent.

ljharb avatar Jan 31 '19 15:01 ljharb

I'm not opposed to the idea, on the surface it seems good. There are two concerns with this though:

  1. Web compat; does there exist an in-the-wild assignment of Array.prototype.get/Array.prototype.set?

  2. This would likely conflict with #18 and support for TypedArray views which already have a .set method with a different signature and purpose. (mdn, spec)

keithamus avatar Aug 19 '19 14:08 keithamus

Yeah, I think that was the "I remember there were arguments against those methods, but forgot what they were".

I think this would be very worth it, even renamed for compat issues, getItem/setItem, or getAt/setAt, or possibly itemAt/setItemAt matching String#charAt.

Zarel avatar Aug 20 '19 00:08 Zarel

Edit: formatting

What about array.at(-1) for reading, array.put(-1, value) for writing, and array.index(-1) to resolve indices? array.index(n) would return -1 if n out of bounds and the rest would throw, for safety and sanity.

Here's an optimized polyfill, derived from my polyfill of the current proposal here: https://github.com/keithamus/proposal-array-last/issues/26

const floor = Math.floor

function computeIndex(l, n) {
  l = floor(l)
  n = floor(n)
  if (l !== l || n !== n) return 0
  if (l >= 0) {
    if (l > 9007199254740991) l = 9007199254740991
    if (n < 0) {
		n += len
		if (n <= 0) return 0
	}
    if (n < l) return n
  }
  return -1
}

function install(name, func) {
  if (Object.getOwnPropertyDescriptor(Array.prototype, name) == null) {
    Object.defineProperty(Array.prototype, name, {
	  enumerable: false,
	  configurable: false,
	  writable: true,
	  value: func,
	})
  }
}

install("index", function index(n) {
  if (this == null) throw new TypeError("`this` must be coercible to an object")
  return computeIndex(+this.length, +n)
})

install("at", function at(n) {
  if (this == null) throw new TypeError("`this` must be coercible to an object")
  const index = computeIndex(+this.length, +n)
  if (index < 0) throw new RangeError("index is out of range")
  return this[index]
})

install("put", function put(n, value) {
  if (this == null) throw new TypeError("`this` must be coercible to an object")
  const index = computeIndex(+this.length, +n)
  if (index < 0) throw new RangeError("index is out of range")
  this[index] = value
})

This implements the following algorithm:

Abstract Operation: ResolveIndex(O, offset)

  1. ! RequireObjectCoercible(O).
  2. Let length be ? ToLength(Get(O, "length")).
  3. Let realOffset be ? ToInteger(offset).
  4. If realOffset is +∞, return -1.
  5. If realOffset is -∞, return 0.
  6. If realOffset < 0, set realOffset to max(realOffset + length, 0).
  7. If realOffset < length, return realOffset.
  8. Return -1.

Array.prototype.index(offset)

  1. Let O be the this value.
  2. ? RequireObjectCoercible(O).
  3. Return ResolveIndex(O, offset).

Array.prototype.at(offset)

  1. Let O be the this value.
  2. ? RequireObjectCoercible(O).
  3. Let index be ? ResolveIndex(O, offset).
  4. If index is -1, throw a RangeError exception.
  5. Return Get(O, ! ToString(index)).

Array.prototype.put(offset, value)

  1. Let O be the this value.
  2. ? RequireObjectCoercible(O).
  3. Let index be ? ResolveIndex(O, offset).
  4. If index is -1, throw a RangeError exception.
  5. Return Set(O, ! ToString(index), value).

dead-claudia avatar Dec 12 '19 05:12 dead-claudia