Can an Array have array indexed accessor properties and other curiosities??

# Allen Wirfs-Brock (17 years ago)

In Mountain View and/or in earlier discussions there was agreement that Array instances needed a special version of [[DefineOwnProperty]] that ensures that the array length invariants are properly maintained in the presence of Object.defineProperty and friends. I'm working on that now and running into several cases that I don't believe have been previously discussed (at least for ES3.1) and have not been adequately address in the special array [[ThrowingPut]].

To start, let's assume that using [[DefineOwnProperty]] to set the value of a property whose name is an array index potentially adjusts the value of the length property, just like [[Put]] did in ES3. Also remember that the length property is born [[Configurable]]: false.

What happens, if the [[DefineOwnProperty]] is used to create an accessor property of an array instance whose name is an array index. I've thought of three possibilities:

  1. It's disallowed, Array instances can't have own accessor properties with array index names. (and since Array.prototype is an Array instance it can't either. An array instance could only inherit an accessor property with an array index name from Object.prototype)

  2. It's allowed, but defining such a property doesn't cause length to change (when the array index name is >= the current length) and explicitly reducing the length does not delete such properties.

  3. They are treated just like data properties WRT the length invariant.

My preference is #1. Accessor properties are new to the standard and we get to decide where they are and aren't allowed. Excluding them from indexed array properties eliminates some complicating edge cases and may place less of a burden on implementations that want to optimized array representations. However, it may (??) impact existing implementation that already have arrays that allow getter/setter methods.

Opinions?

Here's another one. What happens if the length field is set [[Writable]]: false and an attempt is subsequently make to define an array indexed property (using either [[Put]] or Object.defineProperty) whose name is >= than length? I suggest it fails (silently or not depending upon the Throw parameter).

What happens if the length is explicitly reduced such that an array indexed property that is [[Configurable]]: false would be deleted by the ES3 array [[Put]] algorithm? Alternatives: 1) The undeletable property is left undisturbed. (This tosses out the ES3 invariant that length is > than the name of any array indexed own properties. 2) The redefinition of length fails (silently or not depending upon the Throw parameter).

My preference is #1. #2 might be more consistent with my read-only length recommendation but I'm concern that it requires implementations to use a two pass algorithm to ensure atomicity. I'd prefer that not to impose that on implementers.

In summary, because accessor properties and program controllable attribute values are new in ES3.1 we have some flexibility in specifying these behaviors. I think we should choose the alternatives that avoids adding to the semantic complexity of arrays and minimizes implementation complexity.

# Breton Slivka (17 years ago)
  1. It's disallowed, Array instances can't have own accessor properties with array index names. (and since Array.prototype is an Array instance it can't either. An array instance could only inherit an accessor property with an array index name from Object.prototype)

(snip)

My preference is #1. Accessor properties are new to the standard and we get to decide where they are and aren't allowed. ... (snip)

Opinions?

Arrays already have a "magical" length property. may as well make indexes "magic" as well. As a user, I can't think of any particular use for making certain array indexes read only.

Here's another one. What happens if the length field is set [[Writable]]: false and an attempt is subsequently make to define an array indexed property (using either [[Put]] or Object.defineProperty) whose name is >= than length? I suggest it fails (silently or not depending upon the Throw parameter).

I like this. It means you can make essentially fixed length arrays. Perhaps there is even room for implementation optimisation here if the user does this?

What happens if the length is explicitly reduced such that an array indexed property that is [[Configurable]]: false would be deleted by the ES3 array [[Put]] algorithm? Alternatives:

           1) The undeletable property is left undisturbed.  (This

tosses out the ES3 invariant that length is > than the name of any array indexed own properties.

           2) The redefinition of length fails (silently or not

depending upon the Throw parameter).

My preference is #1. #2 might be more consistent with my read-only length recommendation but I'm concern that it requires implementations to use a two pass algorithm to ensure atomicity. I'd prefer that not to impose that on implementers.

what if you had #1, but the length property, rather than being set to the new value, gets set to the index of the last undeletable property

    1. That's what I would expect to happen.
# Mark S. Miller (17 years ago)

2009/2/13 Allen Wirfs-Brock <Allen.Wirfs-Brock at microsoft.com>:

What happens, if the [[DefineOwnProperty]] is used to create an accessor property of an array instance whose name is an array index. I've thought of three possibilities:

  1. It's disallowed, Array instances can't have own accessor properties with array index names. (and since Array.prototype is an Array instance it can't either. An array instance could only inherit an accessor property with an array index name from Object.prototype)

  2. It's allowed, but defining such a property doesn't cause length to change (when the array index name is >= the current length) and explicitly reducing the length does not delete such properties.

  3. They are treated just like data properties WRT the length invariant.

My preference is #1. Accessor properties are new to the standard and we get to decide where they are and aren't allowed. Excluding them from indexed array properties eliminates some complicating edge cases and may place less of a burden on implementations that want to optimized array representations. However, it may (??) impact existing implementation that already have arrays that allow getter/setter methods.

My preference is #3, closely followed by #1. If implementation or legacy constraints would favor #1, that's fine.

.#2 just seems weird and irregular to me. I am against it. #2 would mean that -- even without using the new Object meta methods -- accessor properties become less transparent virtualizations of data properties. It also loses the invariant that you mention below: that "length is > than the name of any array indexed own properties."

Even #3 does not enable the creation of array P that acts as a transparent proxy for array Q, where P and Q are genuine arrays, since there's no way for P's length to track changes to Q's length. So I see no strong argument for #3 over #1.

Here's another one. What happens if the length field is set [[Writable]]: false and an attempt is subsequently make to define an array indexed property (using either [[Put]] or Object.defineProperty) whose name is >= than length? I suggest it fails (silently or not depending upon the Throw parameter).

I agree.

What happens if the length is explicitly reduced such that an array indexed property that is [[Configurable]]: false would be deleted by the ES3 array [[Put]] algorithm? Alternatives:

           1) The undeletable property is left undisturbed.  (This

tosses out the ES3 invariant that length is > than the name of any array indexed own properties.

           2) The redefinition of length fails (silently or not

depending upon the Throw parameter).

My preference is #1. #2 might be more consistent with my read-only length recommendation but I'm concern that it requires implementations to use a two pass algorithm to ensure atomicity. I'd prefer that not to impose that on implementers.

As mentioned above, I think we should not lose that invariant, so I am against choice #1. Absent possible implementation constraints, I like choice #2. However, to avoid the two-phase issue, how about

  1. Delete from the end until we can't
  • length is set to max(1+largest array index of a non-configurable property, attemptedNewLength).
  • all array indexed properties >= new value of length are deleted
  • if the new value of length != attemptedNewLength, then throw.

This #3 has the problem that it is not failure atomic: on failure, it has partially changed the array. But it does preserve the invariant.

In summary, because accessor properties and program controllable attribute values are new in ES3.1 we have some flexibility in specifying these behaviors. I think we should choose the alternatives that avoids adding to the semantic complexity of arrays and minimizes implementation complexity.

Agreed.

# Mark S. Miller (17 years ago)

On Fri, Feb 13, 2009 at 7:12 PM, Breton Slivka <zen at zenpsycho.com> wrote:

Here's another one. What happens if the length field is set [[Writable]]: false and an attempt is subsequently make to define an array indexed property (using either [[Put]] or Object.defineProperty) whose name is >= than length? I suggest it fails (silently or not depending upon the Throw parameter).

I like this. It means you can make essentially fixed length arrays. Perhaps there is even room for implementation optimisation here if the user does this?

Good point.

What happens if the length is explicitly reduced such that an array indexed property that is [[Configurable]]: false would be deleted by the ES3 array [[Put]] algorithm? Alternatives:

           1) The undeletable property is left undisturbed.  (This

tosses out the ES3 invariant that length is > than the name of any array indexed own properties.

           2) The redefinition of length fails (silently or not

depending upon the Throw parameter).

My preference is #1. #2 might be more consistent with my read-only length recommendation but I'm concern that it requires implementations to use a two pass algorithm to ensure atomicity. I'd prefer that not to impose that on implementers.

what if you had #1, but the length property, rather than being set to the new value, gets set to the index of the last undeletable property

    1. That's what I would expect to happen.

Good. My proposed #3 is in agreement with this modulo the issue of whether it throws. I should have specified that it throws conditional on the Throw parameter (i.e., only if the assignment appears in strict code).

# Brendan Eich (17 years ago)

On Feb 15, 2009, at 10:23 AM, Mark S. Miller wrote:

2009/2/13 Allen Wirfs-Brock <Allen.Wirfs-Brock at microsoft.com>:

What happens, if the [[DefineOwnProperty]] is used to create an
accessor property of an array instance whose name is an array index. I've
thought of three possibilities:

  1. It's disallowed, Array instances can't have own accessor
    properties with array index names. (and since Array.prototype is an Array instance
    it can't either. An array instance could only inherit an accessor property
    with an array index name from Object.prototype)

  2. It's allowed, but defining such a property doesn't cause length
    to change (when the array index name is >= the current length) and explicitly
    reducing the length does not delete such properties.

  3. They are treated just like data properties WRT the length
    invariant.

My preference is #1. Accessor properties are new to the standard
and we get to decide where they are and aren't allowed. Excluding them from
indexed array properties eliminates some complicating edge cases and may
place less of a burden on implementations that want to optimized array
representations. However, it may (??) impact existing implementation that already have arrays that allow getter/setter methods.

My preference is #3, closely followed by #1. If implementation or legacy constraints would favor #1, that's fine.

Implementations want 3, so do users. SpiderMonkey js shell sessions:

js> a = [1,2,3]

1,2,3 js> a.defineGetter(9, function()42)

js> a.length

10 js>

Yoyodyne:src brendaneich$ ./Darwin_DBG.OBJ/js js> a = [0,1,2]

0,1,2 js> a.length

3 js> a.defineGetter(9, function(){return 9})

js> a.length

10 js> a

0,1,2,,,,,,,9

#2 just seems weird and irregular to me. I am against it. #2 would mean that -- even without using the new Object meta methods -- accessor properties become less transparent virtualizations of data properties. It also loses the invariant that you mention below: that "length is > than the name of any array indexed own properties."

Agreed.

Even #3 does not enable the creation of array P that acts as a transparent proxy for array Q, where P and Q are genuine arrays, since there's no way for P's length to track changes to Q's length. So I see no strong argument for #3 over #1.

Existing practice favors 3 over 1. That's decisive in my opinion
(getters and setters are a de-facto standard ES3.1 is codifying and
improving).

  1. Delete from the end until we can't
  • length is set to max(1+largest array index of a non-configurable property, attemptedNewLength).
  • all array indexed properties >= new value of length are deleted
  • if the new value of length != attemptedNewLength, then throw.

This #3 has the problem that it is not failure atomic: on failure, it has partially changed the array. But it does preserve the invariant.

(Plea for A/B/C after different 1/2/3 numbered list style!)

So (reading ahead one message) I join Mark in agreeing with Breton's
followup proposal, or amendment to the second #1 (if you know what I
mean! :-P).

# Allen Wirfs-Brock (17 years ago)

-----Original Message----- From: Brendan Eich [mailto:brendan at mozilla.com] Sent: Sunday, February 15, 2009 10:43 AM ...

What happens, if the [[DefineOwnProperty]] is used to create an accessor property of an array instance whose name is an array index. I've thought of three possibilities: ... Implementations want 3, so do users. SpiderMonkey js shell sessions: ... Existing practice favors 3 over 1. That's decisive in my opinion (getters and setters are a de-facto standard ES3.1 is codifying and improving).

It would be nice to have some concrete examples to backup the assertions in the first quote above. Does anyone know of actual use cases or concrete examples where users have exploited this capability in interesting ways. Similarly, can we be more concrete about why implementers would favor #3 (other than that it would make it easier for existing implementations that already do #3 to support ES3.1. That is a reasonable consideration what I acknowledge). I can think of assorted ways that #1 might benefit implementers who are interested in various optimizations of arrays. It's less obvious to me that #1 would be detrimental to implementations and #3 would be beneficial.

It great that we have existing practice to draw upon. We also have some wiggle room to depart from existing practice if we believe that would be beneficial. It would be nice if there was some strong evidence based upon actual experience that pointed one way or another.

... (Plea for A/B/C after different 1/2/3 numbered list style!)

Agreed, I've learned my lesson about having multiple ambiguously numbered listed in one message.

So (reading ahead one message) I join Mark in agreeing with Breton's followup proposal, or amendment to the second #1 (if you know what I mean! :-P).

Seems, OK to me although I'm not 100% sold that throwing in strict mode makes sense in this situation.

BTW, this week's Wiki draft will reflect my original suggestions as Pratap is already working on preparing it for Wiki publication. We'll update next week's draft to reflect whatever wise decided here.

# Brendan Eich (17 years ago)

On Feb 15, 2009, at 12:14 PM, Allen Wirfs-Brock wrote:

-----Original Message----- From: Brendan Eich [mailto:brendan at mozilla.com] Sent: Sunday, February 15, 2009 10:43 AM ...

What happens, if the [[DefineOwnProperty]] is used to create an accessor property of an array instance whose name is an array index. I've thought of three possibilities: ... Implementations want 3, so do users. SpiderMonkey js shell sessions: ... Existing practice favors 3 over 1. That's decisive in my opinion (getters and setters are a de-facto standard ES3.1 is codifying and improving).

It would be nice to have some concrete examples to backup the
assertions in the first quote above. Does anyone know of actual use
cases or concrete examples where users have exploited this
capability in interesting ways.

mxr.mozilla.org/mozilla-central/source/js/narcissus/jsexec.js#666, mxr.mozilla.org/mozilla-central/source/js/narcissus/jsexec.js#869

I know of other array workalikes and proxies built this way; I'll post
source links if I can find any.

Now I hope you're gonna quibble about what is "interesting"!

Similarly, can we be more concrete about why implementers would
favor #3 (other than that it would make it easier for existing
implementations that already do #3 to support ES3.1. That is a
reasonable consideration what I acknowledge).

Implementation hardship is minor: all array implementations that
optimize for the dense case have to fall back on a more Object-like
sparse case (hash table), or mix in the latter to the former
implementation somehow. Defining an accessor simply triggers that
fallback or hybridization, as do sparseness and possibly other criteria.

I can think of assorted ways that #1 might benefit implementers who
are interested in various optimizations of arrays. It's less obvious
to me that #1 would be detrimental to implementations and #3 would
be beneficial.

The main point is users, not implementors. Implementation hardship is
a consideration, but secondary and (from experience, and from looking
at other open source implementations) not a problem in this case.

# Allen Wirfs-Brock (17 years ago)

-----Original Message----- From: Brendan Eich [mailto:brendan at mozilla.com] Sent: Sunday, February 15, 2009 12:41 PM ...

It would be nice to have some concrete examples to backup the assertions in the first quote above. Does anyone know of actual use cases or concrete examples where users have exploited this capability in interesting ways.

mxr.mozilla.org/mozilla-central/source/js/narcissus/jsexec.js#666, mxr.mozilla.org/mozilla-central/source/js/narcissus/jsexec.js#869

Interesting, but not actually what I was asking about. Unless I'm missing something, in both these cases "array index" named properties are being defined on regular objects, not instances of Array and nothing discussed in this thread so for would interfere with that. What I was asking looking for was example of using "array index" named properties with actual array instances.

# Brendan Eich (17 years ago)

On Feb 15, 2009, at 3:14 PM, Allen Wirfs-Brock wrote:

-----Original Message----- From: Brendan Eich [mailto:brendan at mozilla.com] Sent: Sunday, February 15, 2009 12:41 PM ...

It would be nice to have some concrete examples to backup the assertions in the first quote above. Does anyone know of actual use cases or concrete examples where users have exploited this capability in interesting ways.

mxr.mozilla.org/mozilla-central/source/js/narcissus/jsexec.js#666, mxr.mozilla.org/mozilla-central/source/js/narcissus/jsexec.js#869

Interesting, but not actually what I was asking about. Unless I'm
missing something, in both these cases "array index" named
properties are being defined on regular objects, not instances of
Array and nothing discussed in this thread so for would interfere
with that.

Oh, right -- hence the curses in those comments of mine.

I'll keep looking, but I don't know if I'll find the
define[GS]etter usage you seek. If I'm forced in this little
debate to fall back on general principles, I'll stand with Mark and
oppose any non-virtualizability for Array elements. That still smells
wrong.

What I was asking looking for was example of using "array index"
named properties with actual array instances.

Some implementations, notably v8, generalize their optimizations for
"array index" named properties over all objects, not just Array
instances.