Skip to content
This repository has been archived by the owner on Mar 10, 2024. It is now read-only.

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

Open
Zarel opened this issue Jan 31, 2019 · 6 comments
Open

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

Zarel opened this issue Jan 31, 2019 · 6 comments

Comments

@Zarel
Copy link

Zarel commented Jan 31, 2019

.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.

@Berkmann18
Copy link

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.

@thysultan
Copy link

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.

@ljharb
Copy link
Member

ljharb commented Jan 31, 2019

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

@keithamus
Copy link
Member

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 What about TypedArray views? #18 and support for TypedArray views which already have a .set method with a different signature and purpose. (mdn, spec)

@Zarel
Copy link
Author

Zarel commented Aug 20, 2019

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.

@dead-claudia
Copy link

dead-claudia commented Dec 12, 2019

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: #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).

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants