decoupling [ ] and property access for collections

# Allen Wirfs-Brock (13 years ago)

ES objects and their properties have always had a dual nature. They can be used as both (semi-) fixed-shape object abstraction where the properties are the member names and they can be used as open ended data collections where property names are used as key values for accessing data in the collections.

This dual use is reflected in the two different syntactic forms of property access available in the language, obj.propName and obj[expr]. The dot form uses a property key that is provided by the program author and fixed in the program code. The [ ] uses a runtime computed property key that is typically not known when the program is written.

Using a single semantic concept for both these purposes is problematic in several ways: 1) It conflates the application data domain with the program definition domain. This is a pretty clear abstraction layering violation 2) It is confusing to programmer coming to JS from other language who think of . and [ ] as distinctly different operations. 3) It makes it difficult to define real collection objects. If [ ] is used for collection access then collection elements must be represented as properties and their keys may conflict with actual property names. There are also other issues such as implicitly restricting collection keys to be string values.

.#3 is currently resolve in one of two ways. Either collections (such as as the proposed Map and Set) use explicitly method calls for element access and don't support element access via [ ] or they expose the collection as an object with a null [[Prototype]] value. This allows [ ] to be used for (string keyed) element access but requires a separate manager object to provide functions that operate upon the collection.

So, is there a way that we can make [ ] usable as an extensible collection element accessor while still preserving its property accessor duality for regular JS objects and both legacy and future code that depends upon that duality. Perhaps, here is how we might do it:

ES5 11.2.1 currently couples . and [ ] by saying that MemberExpresson . IdentifierName desugars to MemberExpression [ <identifier-name-string> ]

we start decoupling by eliminating the above desugaring and instead simply use for MemberExpresson . IdentifierName the existing 11.2.1 algorithm (in a slight simplified form because we know the property name is already a string).

We then give MemberExpression [ Expression ] a new semantics. Here is the initial skeleton of this new semantics:

 if MemberExpression is a "collection" return the result of invoking its "element getter/setter method"
 else do the algorithm from ES5 11.2.1

(in the actual specification this would need to be expressed in terms of a special kind of Reference value and GetValue/PutValue, but I'm ignoring the details of References for this overview).

What this says is that for any existing ES5 style object continue to work just like they always have. But in ES.Harmony there would be a new kind of "collection" object for which . works differently from [ ].

(Dave Herman has another way to say this: [ ] and . can be viewed as operating on two separate property name spaces, but for legacy/normal ES objects those namespaces are collapsed into a single shared namespace.)

So to make the above skeleton semantics more meaningful we need to define what we really mean by "collection" and by "element getter/setter method". Let's start with the latter.

Let assume that there are two predefined private name object values that are required to exist by the ES.Harmony spec. Let's refer to those values as @elementGetKey and @elementSetKey (these are just names we use in the spec. language to talk about those private name values, the actual private name objects would be dynamically provided by ES implementations). Then a "element getter/setter method" is simply an object property whose property key is either @elementGetKey or @elementSetKey. The signature of these methods would normally be: function /*element getter */ (elementKey){return anElementValue};
function /*element setter */ (elementKey, newElementValue){};

Further more we define "collection" to mean an object that has a property that is a "element getter/setter method". The property may be either own or inherited.

How would bwe define such an "collection" object. It could be as simply as something like this:

import {collectionGetter, collectionSetter} from "@metaCollections";

export function StringKeyedMap() { this.__content = Object.create(null); //note __content object is a "normal object" and [ ] on it does regular property access Object.defineProperty(this, collectionGetter,{value: function(k) {return this.__content[k]}); Object.defineProperty(this, collectionSetter,{value: function(k,v) {this.__content[k]=v;}); this.size = function() {Object.getOwnPropertyNames(this.__content ).length}; //I'm lazy this.has = function(k) {return {}.hasOwnProperty.call(this.__content,k}; this.delete = function(k) {return delete this.__content[k]} }

This implements a string-keyed map with the same interface as used in the simple _map proposal, except that [ ] is used instead of get/set methods for element access. Note that there is no conflict between element names and method names such as "size" and "has". It uses as backing store a regularly object that acts as an string-keyed hash table.

Using this techniques all sorts of "collection" classes could be build including array-like collections with domain restrictions of their element values. They would all be fully "subclassable".

However, there is one significant issue I see in this approach. This is a usage conflict between [ ] as a collection element accessor and [ ] as a private state accessor. This issue is the reason I don't use a private name for the __content property above and the the reason I used defineProperty to install the element getter/setter methods.

The problem is that up to now we have said that we will define private properties using private Name object keys and that regular [ ] property access with a private name value would be used to access that state. This convention is in direct conflict with my revised definition of [ ] to do collection element access. Using the current private name convention, the way I would have liked to have defined StringKeyedMap is:

import {collectionGetter, collectionSetter} from "@metaCollections"; content = Name.create(); export function StringKeyedMap() { this[content] = Object.create(null); //note content object is a "normal object" and [ ] on it does regular property access this[collectionGetter] = function(k) {return this[content][k]}; this[collectionSetter] = function(k,v) {this[content][k]=v;}; this.size = function() {Object.getOwnPropertyNames(this[content]).length}; //I'm lazy this.has = function(k) {return {}.hasOwnProperty.call(this[content],k}; this.delete = function(k) {return delete this[content][k]} }

However, all the inner this[content] calls would result in an infinite recursion on this[collectionGetter].

This would seem to be a fundamental conflict between using [ ] to access private properties and using [ ] as a special collection element accessor syntax. You might take this as problem with using [ ] as a collection accessor or as a discovering that using [ ] as a private property access or is not very future proof. I think that the long term appeal of using [ ] for collections is compelling enough that we should re-consider the private name usage and define perhaps define new syntax/semantics for private name access. Here is a first cut:

MemberExpression : MemberExpression @ Identifier

where the semantics is that Identifier is evaluated as a variable reference and its value is used as a private name keyed property lookup. Throws if the value of the variable is not a private name object. We would then write my example as:

import {collectionGetter, collectionSetter} from "@metaCollections"; content = Name.create(); export function StringKeyedMap() { this at content = Object.create(null); //note content object is a "normal object" and [ ] on it does regular property access this at collectionGetter = function(k) {return this at content [k]}; this at collectionSetter = function(k,v) {this at content [k]=v;}; this.size = function() {Object.getOwnPropertyNames(this at content ).length}; //I'm lazy this.has = function(k) {return {}.hasOwnProperty.call(this at content,k}; this.delete = function(k) {return delete this at content [k]} }

# Dean Landolt (13 years ago)

On Mon, Oct 17, 2011 at 4:30 PM, Allen Wirfs-Brock <allen at wirfs-brock.com>wrote:

ES objects and their properties have always had a dual nature. They can be used as both (semi-) fixed-shape object abstraction where the properties are the member names and they can be used as open ended data collections where property names are used as key values for accessing data in the collections.

This dual use is reflected in the two different syntactic forms of property access available in the language, obj.propName and obj[expr]. The dot form uses a property key that is provided by the program author and fixed in the program code. The [ ] uses a runtime computed property key that is typically not known when the program is written.

Using a single semantic concept for both these purposes is problematic in several ways: 1) It conflates the application data domain with the program definition domain. This is a pretty clear abstraction layering violation

How so? I think you may be playing fast and loose with the word "domain" here. ISTM the "domain" that matters is the value domain of the object property names. In es5 and earlier this value domain is strictly string-based (even array keys), with an object's toString as the identity function. In es-next this value domain is set to be expanded to allow objects with extrinsic identity (private name objects). Thus, the new value domain for property names in es-next is slated to be the union of these disjoint spaces. I can't see how this is problematic.

Dot access has always been nothing more than sugar -- a strict subset of the domain of legal property names. This subset excludes private names just as it excludes a whole range of strings, and for the same reason (insufficient syntax support). Arguing for syntax support is one thing -- and I'm not trying to claim that there is no room for it -- but this is a flawed premise.

2) It is confusing to programmer coming to JS from other language who

think of . and [ ] as distinctly different operations.

Doesn't this prioritize the (assumed) expectations of users of other languages over the expectations of users of es-current? Applying the same rationale, es-current users should be be equally confused by such a fundamental change to the language when migrating to harmony, right? This is

[snip]

What this says is that for any existing ES5 style object continue to work just like they always have. But in ES.Harmony there would be a new kind of "collection" object for which . works differently from [ ].

(Dave Herman has another way to say this: [ ] and . can be viewed as operating on two separate property name spaces, but for legacy/normal ES objects those namespaces are collapsed into a single shared namespace.)

IMHO the single "property name space" of es-current is a feature, not a bug. Maybe it was a mistake to use "toString" as the identity function (and perhaps this can rectified...with private names, even :D). But this alternative view of the property name space doesn't smell right -- it certainly doesn't smell anything like javascript. Treating dot access as anything more than static sugar for bracket access would be an enormous change to the language! The consequences would be pretty far-reaching (for instance, what's the impact on proxies?). There has to be an easier way to get sane collections, right?

[snipped the rest]

# Erik Arvidsson (13 years ago)

YES! This is the most exciting proposals I've seen in years. I'm so excited!

With this we would be able to implement Array and all those pesky DOM collections without a hitch [*]. I think it might even allow sub classing Array if Array was reimlemented using this mechanism.

[*] What of Object.keys and other reflection like mechanisms? Should we have a collectionKeys private name too?

# Axel Rauschmayer (13 years ago)

Dot access has always been nothing more than sugar -- a strict subset of the domain of legal property names. This subset excludes private names just as it excludes a whole range of strings, and for the same reason (insufficient syntax support). Arguing for syntax support is one thing -- and I'm not trying to claim that there is no room for it -- but this is a flawed premise.

Very few languages have the ability to directly create objects with methods "ex nihilo". In most mainstream languages, you need a class to do so. In that role, objects map names to values. If you use them in that role then compilers can statically analyze. That names are strings is an implementation detail. [] access is a very powerful feature and exploits that implementation detail.

Object literals look very similar to dictionary/map literals which is why it is so easy to conflate objects and maps. But if you come from a static language such as Java, you tend to be more aware of the difference.

  • Pro: This conflation makes JavaScript very flexible.
  • Cons: We only get poor man’s maps via objects. Most other dynamic languages (Python, Ruby, Groovy) allow any value as a key.
  • Cons: Name collisions between map entries and properties is a constant source of trouble. For example, I suspect that the enumerable attribute of properties would not be needed if we didn’t have this conflation.
  • Cons: The more one uses [], the less a compiler can statically analyze/optimize.

If we ever have maps with non-string keys then accessing elements should probably be done via a method.

var map = new Map(); map.put(true, "Yes"); map.put(false, "No"); console.log(map.get(true));

I wouldn’t mind that (but I’ve coded a lot of Java, so that is to be expected). You can get map pseudo-literals by chaining put() method calls.

# David Herman (13 years ago)

(Dave Herman has another way to say this: [ ] and . can be viewed as operating on two separate property name spaces, but for legacy/normal ES objects those namespaces are collapsed into a single shared namespace.)

Lest the above be construed as a tacit approval on my part... ;)

IMHO the single "property name space" of es-current is a feature, not a bug.

I tend to agree. There are expressibility problems, too. For example, if you have an object that uses . for its API and [] for some other data, then what do you do when you want to dynamically compute an API name? I would hope not

eval("obj." + computeName())

But I don't see any obvious ways out of this that aren't pretty convoluted.

# Axel Rauschmayer (13 years ago)
  • Pro: This conflation makes JavaScript very flexible.
  • Con: We only get poor man’s maps via objects. Most other dynamic languages (Python, Ruby, Groovy) allow any value as a key.
  • Con: Name collisions between map entries and properties is a constant source of trouble. For example, I suspect that the enumerable attribute of properties would not be needed if we didn’t have this conflation.
  • Con: The more one uses [], the less a compiler can statically analyze/optimize.

To clarify: I’m arguing in favor of not abusing objects as maps from strings to values (for data). The "Pro" does not really fit into this line of arguing. Neither does the last con.

# Allen Wirfs-Brock (13 years ago)

On Oct 17, 2011, at 2:38 PM, Dean Landolt wrote:

On Mon, Oct 17, 2011 at 4:30 PM, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote: ES objects and their properties have always had a dual nature. They can be used as both (semi-) fixed-shape object abstraction where the properties are the member names and they can be used as open ended data collections where property names are used as key values for accessing data in the collections.

This dual use is reflected in the two different syntactic forms of property access available in the language, obj.propName and obj[expr]. The dot form uses a property key that is provided by the program author and fixed in the program code. The [ ] uses a runtime computed property key that is typically not known when the program is written.

Using a single semantic concept for both these purposes is problematic in several ways: 1) It conflates the application data domain with the program definition domain. This is a pretty clear abstraction layering violation

How so? I think you may be playing fast and loose with the word "domain" here. ISTM the "domain" that matters is the value domain of the object property names. In es5 and earlier this value domain is strictly string-based (even array keys), with an object's toString as the identity function. In es-next this value domain is set to be expanded to allow objects with extrinsic identity (private name objects). Thus, the new value domain for property names in es-next is slated to be the union of these disjoint spaces. I can't see how this is problematic.

Dot access has always been nothing more than sugar -- a strict subset of the domain of legal property names. This subset excludes private names just as it excludes a whole range of strings, and for the same reason (insufficient syntax support). Arguing for syntax support is one thing -- and I'm not trying to claim that there is no room for it -- but this is a flawed premise.

(Note that I didn't say anything about private names in the above statement. Here I was talking about string valued names. The private name issue is related, but wasn't my primary concern for this point.

I'm primarily using "domain" in the sense as it is used in "domain modeling". The set of entities that are elements of some specific problem space for which we are trying to construct a software based solutions. Confusion often reigns when someone intermingles entities that are part of independent domain models. Particularly, if the same words are used to label unrelated concepts within the different models.

But let me try to make the same point, a different way. Method names, function names, variable names, etc. are meta-data about the program. They name entities that are the parts of a program expressed in some specific programming language. You use such names to construct, access and manipulate the actual definition of a program (in other words, for meta-programming). They are not entities of the problem domain within which the program operates. Some of us consider it a best practice to stratify the meta-programming level and the application problem domain level. Keep them at different application layers with only limited intentional cross-over between the layers. For me, the desirability of this stratified approach comes from working with many difficult to maintain programs written in languages where it was too easy to unintentionally merge the application and meta-programming strata.

Combining names from the meta-programming domain (for example method names) with names from the application domain in the same namespace is not a good thing to do. The fact that JS to some degree encourages and sometimes even requires this is not really one of its desirable characteristics.

2) It is confusing to programmer coming to JS from other language who think of . and [ ] as distinctly different operations.

Doesn't this prioritize the (assumed) expectations of users of other languages over the expectations of users of es-current? Applying the same rationale, es-current users should be be equally confused by such a fundamental change to the language when migrating to harmony, right? This is

It seems to me that for various features we at least consider that exact prioritization. However, this issue is the only motivation.

It certainly is the case today that a competent JS programmer needs to understand the duality of . and [ ]. That said, it isn't clear what percentage of JS programmers qualify as competent. There is certainly plenty of code around that accesses properties as obj["name"] or do computed property access via: eval("obj."+key]. Regardless, my proposal doesn't change this duality as the default. It just allows the implementor of collection like abstraction to choose something other than the default.

BTW, there is already confusion in the DOM that relates to confusion between collection elements and object properties. See: dev.w3.org/2006/webapi/WebIDL/#OverrideBuiltins, lists.w3.org/Archives/Public/public-script-coord/2011AprJun/0031.html

[snip]

What this says is that for any existing ES5 style object continue to work just like they always have. But in ES.Harmony there would be a new kind of "collection" object for which . works differently from [ ].

(Dave Herman has another way to say this: [ ] and . can be viewed as operating on two separate property name spaces, but for legacy/normal ES objects those namespaces are collapsed into a single shared namespace.)

IMHO the single "property name space" of es-current is a feature, not a bug. Maybe it was a mistake to use "toString" as the identity function (and perhaps this can rectified...with private names, even :D). But this alternative view of the property name space doesn't smell right -- it certainly doesn't smell anything like javascript. Treating dot access as anything more than static sugar for bracket access would be an enormous change to the language! The consequences would be pretty far-reaching (for instance, what's the impact on proxies?). There has to be an easier way to get sane collections, right?

Certainly, I don't see this duality as being either fundamental or valuable. In fact, I wonder if it isn't one one major reasons that JS seems to be the only modern object-oriented language where nobody seems to have created a widely adopted rich and extensible set of collection classes.

Other that reevaluating how we expose private name access, I don't see the consequences being very far-reaching at all. It would have no effect upon proxies as the new semantics of [ ] decomposes into privatives that already have proxy traps assigned to them. It actually makes proxies more powerful, without changing the Proxy interface in that it allows a proxy to discriminate a . access from a [ ] access.

# Allen Wirfs-Brock (13 years ago)

On Oct 17, 2011, at 3:34 PM, David Herman wrote:

IMHO the single "property name space" of es-current is a feature, not a bug.

I tend to agree. There are expressibility problems, too. For example, if you have an object that uses . for its API and [] for some other data, then what do you do when you want to dynamically compute an API name?

In most languages, this would fall into the realm of the reflection API.

What is the actual frequency of such driven API member selection. If it is high (particularly, high than the utility of good collections) that we may be exposing other problems we need to look at more closely.

I would hope not

eval("obj." + computeName())

But I don't see any obvious ways out of this that aren't pretty convoluted.

I'll give you four:

  1. Object.getOwnPropertyDescriptor(obj,name).value etc.

  2. two new reflection functions: Object.getProperty(obj,name) Object.setProperty(obj,name,value)

  3. build upon the possible alternative private name property syntax let foo='foo'; obj at foo or perhaps obj@('foo')

  4. a switch statement: switch (computedAPIName) { case 'property1': obj.property1(/*args */); break; case 'property2': obj.property2(/*args */); break; case 'property3': // etc. }

All of these start from the perspective that this sort of reflective API access should be quite rare.

# Axel Rauschmayer (13 years ago)

This would seem to be a fundamental conflict between using [ ] to access private properties and using [ ] as a special collection element accessor syntax. You might take this as problem with using [ ] as a collection accessor or as a discovering that using [ ] as a private property access or is not very future proof. I think that the long term appeal of using [ ] for collections is compelling enough that we should re-consider the private name usage and define perhaps define new syntax/semantics for private name access. Here is a first cut:

MemberExpression : MemberExpression @ Identifier

where the semantics is that Identifier is evaluated as a variable reference and its value is used as a private name keyed property lookup. Throws if the value of the variable is not a private name object. We would then write my example as:

import {collectionGetter, collectionSetter} from "@metaCollections"; content = Name.create(); export function StringKeyedMap() { this at content = Object.create(null); //note content object is a "normal object" and [ ] on it does regular property access this at collectionGetter = function(k) {return this at content [k]}; this at collectionSetter = function(k,v) {this at content [k]=v;}; this.size = function() {Object.getOwnPropertyNames(this at content ).length}; //I'm lazy this.has = function(k) {return {}.hasOwnProperty.call(this at content,k}; this.delete = function(k) {return delete this at content [k]} }

I agree 100% percent with the observations, but not with the solution.

Shouldn’t this be done the other way around, by introducing a new way of accessing map elements? For example: map@[key] map.[key]

Then we can continue to use . and [] to access properties and use @[] to access data structure elements. I wouldn’t like the asymmetry introduced by using [] for the latter task.

Axel

# Axel Rauschmayer (13 years ago)

I agree 100% percent with the observations, but not with the solution.

Shouldn’t this be done the other way around, by introducing a new way of accessing map elements? For example: map@[key] map.[key]

Then we can continue to use . and [] to access properties and use @[] to access data structure elements. I wouldn’t like the asymmetry introduced by using [] for the latter task.

One more thought: Currently, [] perpetrates the illusion of objects being maps when it is actually closer to a meta-programming mechanism. Not continuing in that direction seems conceptually cleaner to me (even if the syntax will baffle people coming from other programming languages).

# Allen Wirfs-Brock (13 years ago)

On Oct 17, 2011, at 3:32 PM, Erik Arvidsson wrote:

YES! This is the most exciting proposals I've seen in years. I'm so excited!

Thanks...

What started me thinking along this line was noticing how Dart handles [ ] as a dynamically dispatched method. It isn't the first language to do so and C# (and C++, for that matter) does something similar statically. Having an extensible collection access syntax if very handy.

With this we would be able to implement Array and all those pesky DOM collections without a hitch [*]. I think it might even allow sub classing Array if Array was reimlemented using this mechanism.

Exactly, I was thinking the same things. This is largely why I even bothered to float what I'm sure is going to be a controversial idea.

[*] What of Object.keys and other reflection like mechanisms? Should we have a collectionKeys private name too?

I don't see the need. A collection can public expose such methods as normal properties and how they get implemented are up to the collection implementation. If you choose to use a normal object as the backing store of a collection, then the implementation can just use the existing reflection functions. For example, we could easily add keys to by example class:

import {collectionGetter, collectionSetter} from "@metaCollections"; content = Name.create(); export function StringKeyedMap() { this at content = Object.create(null); //note content object is a "normal object" and [ ] on it does regular property access this at collectionGetter = function(k) {return this at content [k]}; this at collectionSetter = function(k,v) {this at content [k]=v;}; this.size = function() {Object.getOwnPropertyNames(this at content ).length}; //I'm lazy this.has = function(k) {return {}.hasOwnProperty.call(this at content,k}; this.delete = function(k) {return delete this at content [k]}; this.keys = function() {return Object.keys(this at content)}; }

# Allen Wirfs-Brock (13 years ago)

On Oct 17, 2011, at 4:16 PM, Axel Rauschmayer wrote:

I agree 100% percent with the observations, but not with the solution.

Shouldn’t this be done the other way around, by introducing a new way of accessing map elements? For example: map@[key] map.[key]

Then we can continue to use . and [] to access properties and use @[] to access data structure elements. I wouldn’t like the asymmetry introduced by using [] for the latter task.

this is plausible and certainly a way to avoid outrage about defining [ ]. I'll have to stew on it a bit to see if I like it better or less.

# David Herman (13 years ago)

I suspect it's not nearly so rare as you think. For example, it just showed up in Tom and Mark's new proxy proposal:

The protect trap is called on Object.{freeze,seal,preventExtensions}(aProxy). The operation argument is a string identifying the corresponding operation (“freeze”, “seal”, “preventExtensions”). This makes it easy for a handler to forward this operation, by e.g. performing Objectoperation.

A simple way to abstract out "do this concrete operation" to "do one of the following set of possible concrete operations" is to pass the name as a string. Yes, there's a certain aesthetic that says that's icky, but JS makes it so convenient that it's the obvious thing to do.

# Brendan Eich (13 years ago)

On Oct 17, 2011, at 5:18 PM, David Herman wrote:

I suspect it's not nearly so rare as you think. For example, it just showed up in Tom and Mark's new proxy proposal:

The protect trap is called on Object.{freeze,seal,preventExtensions}(aProxy). The operation argument is a string identifying the corresponding operation (“freeze”, “seal”, “preventExtensions”). This makes it easy for a handler to forward this operation, by e.g. performing Objectoperation.

A simple way to abstract out "do this concrete operation" to "do one of the following set of possible concrete operations" is to pass the name as a string. Yes, there's a certain aesthetic that says that's icky, but JS makes it so convenient that it's the obvious thing to do.

JS libraries are full of computed method name calls. It's quite common.

codesearch.google.com/#search/&q="](%22%20lang:js&type=cs

# Allen Wirfs-Brock (13 years ago)

On Oct 17, 2011, at 5:24 PM, Brendan Eich wrote:

On Oct 17, 2011, at 5:18 PM, David Herman wrote:

I suspect it's not nearly so rare as you think. For example, it just showed up in Tom and Mark's new proxy proposal:

The protect trap is called on Object.{freeze,seal,preventExtensions}(aProxy). The operation argument is a string identifying the corresponding operation (“freeze”, “seal”, “preventExtensions”). This makes it easy for a handler to forward this operation, by e.g. performing Objectoperation.

A simple way to abstract out "do this concrete operation" to "do one of the following set of possible concrete operations" is to pass the name as a string. Yes, there's a certain aesthetic that says that's icky, but JS makes it so convenient that it's the obvious thing to do.

JS libraries are full of computed method name calls. It's quite common.

codesearch.google.com/#search/&q="](%22%20lang:js&type=cs

(get your searches in while you can...)

Of course, all of these would continue working as written in my proposal. It would only be new kinds of objects that explicitly choose to distinguish computed data accesses from computed property accesses that would be impacted.

Also a fair number of the search requires are regular expressions such as /Opera\s// and quite a few others are obvious data table lookups of functions and not really "method" lookups.

However, this line of argument does reenforce Alex's suggestion of using a new access syntax such as obj@[expr] to explicitly distingish computed data access from computed property access.

# Axel Rauschmayer (13 years ago)

Then we can continue to use . and [] to access properties and use @[] to access data structure elements. I wouldn’t like the asymmetry introduced by using [] for the latter task.

this is plausible and certainly a way to avoid outrage about defining [ ]. I'll have to stew on it a bit to see if I like it better or less.

An equally clean alternative (apologies if this is what you meant all along):

  • Discourage [] for accessing properties via computed names, in favor of a reflective function (if possible as part of a module and not an Object.* method), e.g. getPropertyByName(obj, name) and setPropertyByName(obj, name, value).
  • Let [] default to using these methods on objects and arrays.
  • Use [] freely for collections and dicts. One can use either special name objects, special names or new names for operators, e.g. operator[] and operator[]=

PROBLEM: the easy way of using name objects goes away: this[nameObject]

Thus, there really are two alternatives:

  1. Changing [] in the above manner plus object at nameObject for accessing properties (as you suggested).
  2. [] stays as it is and @[] (perhaps there is something slicker, e.g. .[]) is introduced for collections and dicts.

Axel

# John J Barton (13 years ago)

On Wed, Oct 19, 2011 at 8:30 AM, Axel Rauschmayer <axel at rauschma.de> wrote:

Then we can continue to use . and [] to access properties and use @[] to access data structure elements. I wouldn’t like the asymmetry introduced by using [] for the latter task.

this is plausible and certainly a way to avoid outrage about defining [ ]. I'll have to stew on it a bit to see if I like it better or less.

An equally clean alternative (apologies if this is what you meant all along):

  • Discourage [] for accessing properties via computed names, in favor of a reflective function (if possible as part of a module and not an Object.* method), e.g. getPropertyByName(obj, name) and setPropertyByName(obj, name, value).
  • Let [] default to using these methods on objects and arrays.
  • Use [] freely for collections and dicts. One can use either special name objects, special names or new names for operators, e.g. operator[] and operator[]=

PROBLEM: the easy way of using name objects goes away: this[nameObject]

Thus, there really are two alternatives:

  1. Changing [] in the above manner plus object at nameObject for accessing properties (as you suggested).
  2. [] stays as it is and @[] (perhaps there is something slicker, e.g. .[]) is introduced for collections and dicts.

Instead of creating a lot of complicated rules for 'object', how about creating a new type 'collection'? Code which depended upon a closed, fixed set of types might break, but I guess that risk is small compared to the potential confusion of redefining [].

jjb

# Axel Rauschmayer (13 years ago)

Thus, there really are two alternatives:

  1. Changing [] in the above manner plus object at nameObject for accessing properties (as you suggested).
  2. [] stays as it is and @[] (perhaps there is something slicker, e.g. .[]) is introduced for collections and dicts.

Instead of creating a lot of complicated rules for 'object', how about creating a new type 'collection'?

The result would be simple, but getting to #1 would indeed be a little awkward, because existing code would still work, but would not use best practices.

What are you suggesting? A new primitive? Or a subtype of Object? I don’t think there is a third alternative to those two.

# John J Barton (13 years ago)

On Wed, Oct 19, 2011 at 9:12 AM, Axel Rauschmayer <axel at rauschma.de> wrote:

Thus, there really are two alternatives:

  1. Changing [] in the above manner plus object at nameObject for accessing properties (as you suggested).
  2. [] stays as it is and @[] (perhaps there is something slicker, e.g. .[]) is introduced for collections and dicts.

Instead of creating a lot of complicated rules for 'object', how about creating a new type 'collection'?

The result would be simple, but getting to #1 would indeed be a little awkward, because existing code would still work, but would not use best practices.

Awkwardness of this kind is a best practice ;-)

What are you suggesting? A new primitive? Or a subtype of Object? I don’t think there is a third alternative to those two.

I am suggesting that typeof return "collection" when the RHS defines a new semantic for [] (collection properties) and . (object properties).

jjb

# Axel Rauschmayer (13 years ago)

What are you suggesting? A new primitive? Or a subtype of Object? I don’t think there is a third alternative to those two.

I am suggesting that typeof return "collection" when the RHS defines a new semantic for [] (collection properties) and . (object properties).

Currently you have either primitives or objects. Primitives get methods from their wrapper types. And I think this dichotomy has served JavaScript very well.

Redefining . in a hypothetical collection primitive would have the following problem. For example, dicts [1] are used as follows.

var table = [foo: "howdy", 42: true, "hey, cool!": "not bad, huh?"]; table["foo"]; // "howdy" delete table.foo; table.foo; // undefined typeof table; // "dict" Problem: How do you invoke methods on table? Using AWB’s terminology, accessing properties is part of the program definition domain, accessing collection elements is part of the application data domain. And it’s better not to mix the two. If you look at Java Maps, the separation becomes clear: You use methods to add new collection elements, but have to way to dynamically change the shape of a Map instance.

[1] strawman:dicts [D.H. already mentioned that this proposal does not reflect his current thinking, so beware]

# John J Barton (13 years ago)

On Wed, Oct 19, 2011 at 9:52 AM, Axel Rauschmayer <axel at rauschma.de> wrote:

What are you suggesting? A new primitive? Or a subtype of Object? I don’t

think there is a third alternative to those two.

I am suggesting that typeof return "collection" when the RHS defines a new semantic for [] (collection properties) and . (object properties).

Currently you have either primitives or objects. Primitives get methods from their wrapper types. And I think this dichotomy has served JavaScript very well.

Redefining . in a hypothetical collection primitive would have the following problem. For example, dicts [1] are used as follows.

var table = [foo: "howdy", 42: true, "hey, cool!": "not bad, huh?"]; table["foo"]; // "howdy"delete table.foo; table.foo; // undefinedtypeof table; // "dict"

This example misses the key point: The [] and . namespaces must not

overlap. For example: table["foo"]; // "howdy" not affected by the delete table.foo.

Problem: How do you invoke methods on table?

table.tellAxel = function() {alert('here is a method call');}; table['tellAxel'] = function() {alert('here is a function entry");}; table.tellAxel(); // here is a method call table['tellAxel'].apply(null,[]); // here is a function entry

Using AWB’s terminology, accessing properties is part of the program definition domain, accessing collection elements is part of the application data domain. And it’s better not to mix the two.

Yes, that is exactly the point.

jjb

# Axel Rauschmayer (13 years ago)

Problem: How do you invoke methods on table?

table.tellAxel = function() {alert('here is a method call');}; table['tellAxel'] = function() {alert('here is a function entry");}; table.tellAxel(); // here is a method call table['tellAxel'].apply(null,[]); // here is a function entry

Using AWB’s terminology, accessing properties is part of the program definition domain, accessing collection elements is part of the application data domain. And it’s better not to mix the two.

Yes, that is exactly the point.

So you are arguing in favor of approach #1, right? Then I would make “your” Map a subtype of Object. typeof is currently best limited to primitives (and to distinguishing them from objects), so introducing a new result would suggest adding a new primitive. But I don’t think that is necessary: Once the semantics of [] have been changed in accordance with approach #1 then you can just write a library that provides Maps and other collections.

# John J Barton (13 years ago)

On Wed, Oct 19, 2011 at 10:27 AM, Axel Rauschmayer <axel at rauschma.de> wrote:

Problem: How do you invoke methods on table?

table.tellAxel = function() {alert('here is a method call');}; table['tellAxel'] = function() {alert('here is a function entry");}; table.tellAxel(); // here is a method call table['tellAxel'].apply(null,[]); // here is a function entry

Using AWB’s terminology, accessing properties is part of the program definition domain, accessing collection elements is part of the application data domain. And it’s better not to mix the two.

Yes, that is exactly the point.

So you are arguing in favor of approach #1, right?

No, I am arguing against changing the meaning of [] on 'object'.

Then I would make “your” Map a subtype of Object. typeof is currently best limited to primitives (and to distinguishing them from objects), so introducing a new result would suggest adding a new primitive.

No, the goal is exactly to distinguish collections from objects.

But I don’t think that is necessary: Once the semantics of [] have been changed in accordance with approach #1 then you can just write a library that provides Maps and other collections.

We disagree. I don't want to fix old code, it works, let it be. I want new code to have a great new option. New tools can migrate developers to new options. And these things can happen in our lifetime.

jjb

# Axel Rauschmayer (13 years ago)

So you are arguing in favor of approach #1, right?

No, I am arguing against changing the meaning of [] on 'object'.

Then I would make “your” Map a subtype of Object. typeof is currently best limited to primitives (and to distinguishing them from objects), so introducing a new result would suggest adding a new primitive.

No, the goal is exactly to distinguish collections from objects.

But I don’t think that is necessary: Once the semantics of [] have been changed in accordance with approach #1 then you can just write a library that provides Maps and other collections.

We disagree. I don't want to fix old code, it works, let it be. I want new code to have a great new option. New tools can migrate developers to new options. And these things can happen in our lifetime.

How about approach #2, simply introducing a new kind of []?

Example: .[]

let map = new Map(); map["aNewMethod"] = function () { ... }; // a method map.["first"] = "John Doe"; // a collection element

Advantages:

  • No need to introduce a third kind of value (in addition to objects and primitives).
  • Map is simply a subtype of Object and can be implemented via a library and possibly even ported to ECMAScript 5. Obviously method names such as get() and put() would have to be used instead of .[]
  • No surprise for users: [] currently converts its keys to strings, .[] could be used for other kinds of keys, without breaking the ToString contract of []

It takes some getting used to, but it is conceptually very clear.

Axel

# Allen Wirfs-Brock (13 years ago)

below On Oct 19, 2011, at 10:44 AM, John J Barton wrote:

On Wed, Oct 19, 2011 at 10:27 AM, Axel Rauschmayer <axel at rauschma.de> wrote:

Problem: How do you invoke methods on table?

table.tellAxel = function() {alert('here is a method call');}; table['tellAxel'] = function() {alert('here is a function entry");}; table.tellAxel(); // here is a method call table['tellAxel'].apply(null,[]); // here is a function entry

Using AWB’s terminology, accessing properties is part of the program definition domain, accessing collection elements is part of the application data domain. And it’s better not to mix the two.

Yes, that is exactly the point.

So you are arguing in favor of approach #1, right?

No, I am arguing against changing the meaning of [] on 'object'.

Then I would make “your” Map a subtype of Object. typeof is currently best limited to primitives (and to distinguishing them from objects), so introducing a new result would suggest adding a new primitive.

No, the goal is exactly to distinguish collections from objects.

But I don’t think that is necessary: Once the semantics of [] have been changed in accordance with approach #1 then you can just write a library that provides Maps and other collections.

We disagree. I don't want to fix old code, it works, let it be. I want new code to have a great new option. New tools can migrate developers to new options. And these things can happen in our lifetime.

jjb

It isn't clear what you guys are arguing about.

Under my proposal, old objects and old code operating upon old objects continue to work as it always has. new objects continue to work like old objects unless the new objects have been explicitly defined to have "collection access behavior"

You can consider objects with "collection access behavior" to be a new "type" (I would prefer to say "kind") of object.

If my proposal, you designate this new kind of object by defining a one of the collection accessor properties (could be either well know private names or something like "operator []").

The primitive way to test if an object is a "collection" would be to do a property existence test on a collection accessor property.

I think that trying to define a new typeof value to indicate "collection" would be problematic. What would causes typeof to report "collection"? By doing the property existence test? That would generally make typeof a much more expensive operation and note that the collection accessor methods could be inherited, so it wouldn't just be a own property check. Note that implicit in this proposal is the assume that there could be many different collection types. If the identification of a collection was based upon something other than the existence of certain well known properties then there would have to be some other way to explicitly brand objects as collections when they are defined/created.

Allemn

# Axel Rauschmayer (13 years ago)

It isn't clear what you guys are arguing about.

Under my proposal, old objects and old code operating upon old objects continue to work as it always has. new objects continue to work like old objects unless the new objects have been explicitly defined to have "collection access behavior"

You can consider objects with "collection access behavior" to be a new "type" (I would prefer to say "kind") of object.

If my proposal, you designate this new kind of object by defining a one of the collection accessor properties (could be either well know private names or something like "operator []").

The primitive way to test if an object is a "collection" would be to do a property existence test on a collection accessor property.

I would leave the proposal as it is (no typeof change etc.), only adapt it in either of two ways:

  1. Deprecate [] for property access in non-collection objects and use methods such as Object.getProperty(name) and Object.setProperty(name, value), instead.
  2. Introduce new syntax for collection access, e.g. collection.[key] or collection@[key]

Either of the two adaptations would result in a clear separation of concerns between the application data domain and the program definition domain.

# Axel Rauschmayer (13 years ago)
  1. Deprecate [] for property access in non-collection objects and use methods such as Object.getProperty(name) and Object.setProperty(name, value), instead.

Additionally:

Object.prototype.operator[]get = function (name) { return Object.getProperty(name); } Object.prototype.operator[]set = function (name, value) { return Object.setProperty(name, value); }

# Allen Wirfs-Brock (13 years ago)

On Oct 19, 2011, at 12:07 PM, Axel Rauschmayer wrote:

  1. Deprecate [] for property access in non-collection objects and use methods such as Object.getProperty(name) and Object.setProperty(name, value), instead.

Additionally:

Object.prototype.operator[]get = function (name) { return Object.getProperty(name); } Object.prototype.operator[]set = function (name, value) { return Object.setProperty(name, value); }

In principal, that's how it should work. In practice, since we have to deal with objects that don't inherit from Object.prototype I suspect it is best to leave the fall back behavior as part of the definition of the operator.

Also you need another argument in both of your reflection functions:

Object.prototype.operator[]get = function (name) { return Object.getProperty(this, name) ; } Object.prototype.operator[]set = function (name, value) { return Object.setProperty(this, name, value); }

# John J Barton (13 years ago)

On Wed, Oct 19, 2011 at 11:42 AM, Allen Wirfs-Brock <allen at wirfs-brock.com>wrote:

below

On Oct 19, 2011, at 10:44 AM, John J Barton wrote:

On Wed, Oct 19, 2011 at 10:27 AM, Axel Rauschmayer <axel at rauschma.de>wrote:

Problem: How do you invoke methods on table?

table.tellAxel = function() {alert('here is a method call');}; table['tellAxel'] = function() {alert('here is a function entry");}; table.tellAxel(); // here is a method call table['tellAxel'].apply(null,[]); // here is a function entry

Using AWB’s terminology, accessing properties is part of the program definition domain, accessing collection elements is part of the application data domain. And it’s better not to mix the two.

Yes, that is exactly the point.

So you are arguing in favor of approach #1, right?

No, I am arguing against changing the meaning of [] on 'object'.

Then I would make “your” Map a subtype of Object. typeof is currently best limited to primitives (and to distinguishing them from objects), so introducing a new result would suggest adding a new primitive.

No, the goal is exactly to distinguish collections from objects.

But I don’t think that is necessary: Once the semantics of [] have been changed in accordance with approach #1 then you can just write a library that provides Maps and other collections.

We disagree. I don't want to fix old code, it works, let it be. I want new code to have a great new option. New tools can migrate developers to new options. And these things can happen in our lifetime.

jjb

It isn't clear what you guys are arguing about.

Under my proposal, old objects and old code operating upon old objects continue to work as it always has. new objects continue to work like old objects unless the new objects have been explicitly defined to have "collection access behavior"

You can consider objects with "collection access behavior" to be a new "type" (I would prefer to say "kind") of object.

If my proposal, you designate this new kind of object by defining a one of the collection accessor properties (could be either well know private names or something like "operator []").

Your goal, supporting collections with [] operator, would be widely welcomed and a big improvement for JavaScript. But your proposal uses new features unknown to developers and not (as far as I understand) themselves part of the standard or of implementations. I fear these new features would fail either here or with developers. Thus I suggested an alternative with the same goal but independent of other new features.

Nothing else about my suggestion is important. Reaching the goal of supporting collections is valuable, and I believe the best chance would not build on other new features.

The primitive way to test if an object is a "collection" would be to do a property existence test on a collection accessor property.

I think that trying to define a new typeof value to indicate "collection" would be problematic. What would causes typeof to report "collection"?

Yes.

By doing the property existence test? That would generally make typeof a much more expensive operation and note that the collection accessor methods could be inherited, so it wouldn't just be a own property check.

I don't know how typeof works, but collection objects would have isPrototypeOf(Collection) true. The only difference between Object and Collection would be complete separation of [] and . namespaces. Allowing operator [] to be redefined is independent of the goal here. Consequently I would not expect much inheritance.

Note that implicit in this proposal is the assume that there could be many

different collection types. If the identification of a collection was based upon something other than the existence of certain well known properties then there would have to be some other way to explicitly brand objects as collections when they are defined/created.

Yes, just as must be done now for 'function'.

jjb

# Axel Rauschmayer (13 years ago)

In principal, that's how it should work. In practice, since we have to deal with objects that don't inherit from Object.prototype I suspect it is best to leave the fall back behavior as part of the definition of the operator.

Cool. I don’t care how the details are handled, as long as there is conceptual clarity. Then it all depends on whether JS programmers can accept the new best practice of using Object.getProperty() (or something similar). But it makes tremendous sense: An array becomes more like a collection and an object less like one.

Someone should really work on a collections library for JavaScript – with the above change, we have all the ingredients for doing so.

Another idea for computed property name syntax:

let nameObj = name.create(); let obj = { (nameObj): 0 } obj.(nameObj)++;

Also you need another argument in both of your reflection functions:

Object.prototype.operator[]get = function (name) { return Object.getProperty(this, name) ; } Object.prototype.operator[]set = function (name, value) { return Object.setProperty(this, name, value); }

Correct.

# Allen Wirfs-Brock (13 years ago)

On Oct 19, 2011, at 1:36 PM, John J Barton wrote:

On Wed, Oct 19, 2011 at 11:42 AM, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote: below

It isn't clear what you guys are arguing about.

Under my proposal, old objects and old code operating upon old objects continue to work as it always has. new objects continue to work like old objects unless the new objects have been explicitly defined to have "collection access behavior"

You can consider objects with "collection access behavior" to be a new "type" (I would prefer to say "kind") of object.

If my proposal, you designate this new kind of object by defining a one of the collection accessor properties (could be either well know private names or something like "operator []").

Your goal, supporting collections with [] operator, would be widely welcomed and a big improvement for JavaScript. But your proposal uses new features unknown to developers and not (as far as I understand) themselves part of the standard or of implementations. I fear these new features would fail either here or with developers. Thus I suggested an alternative with the same goal but independent of other new features.

Nothing else about my suggestion is important. Reaching the goal of supporting collections is valuable, and I believe the best chance would not build on other new features.

Well, this is a (potential) ES.Harmony proposal so dependence upon other ES.Harmony features shouldn't be an issue. However, the only such feature my proposal depends upon is Private Names, which has already been accepted for ES.next. Even that isn't strictly necessary if you are willing to use property names like "operator []" for the element accessor methods.

The primitive way to test if an object is a "collection" would be to do a property existence test on a collection accessor property.

I think that trying to define a new typeof value to indicate "collection" would be problematic. What would causes typeof to report "collection"?

Yes.

By doing the property existence test? That would generally make typeof a much more expensive operation and note that the collection accessor methods could be inherited, so it wouldn't just be a own property check. I don't know how typeof works, but collection objects would have isPrototypeOf(Collection) true. The only difference between Object and Collection would be complete separation of [] and . namespaces. Allowing operator [] to be redefined is independent of the goal here. Consequently I would not expect much inheritance.

My experience is exactly the opposite, when you have these sorts of capabilities that people in fact do define rich collection libraries that do make extensive use of inheritance in their implementation.

Note that implicit in this proposal is the assume that there could be many different collection types. If the identification of a collection was based upon something other than the existence of certain well known properties then there would have to be some other way to explicitly brand objects as collections when they are defined/created. Yes, just as must be done now for 'function'.

Function, (and Array, Date, RegExp, etc.) cannot be "subclassed" in ES <= 5. The reason is because their special behavior depends them being direct instances of specific well-known constructors. This sort of limitation is something we need to eliminate. We don't want to added new kinds of objects that have that limitation.

# David Herman (13 years ago)

[1] strawman:dicts [D.H. already mentioned that this proposal does not reflect his current thinking, so beware]

FWIW, I don't really know what my current thinking is. :)

# Waldemar Horwat (13 years ago)

We had this in ES4, together with the provision that you could have multiple arguments between the [], so you could define data structures that can be addressed as:

matrix2d[x, y]

As in here, this would default to the ES5 semantics on objects that don't have the new [] proxy handler.

Overall, I like it. You're already familiar with the main issues that this brings up, such as usage cases where folks want a reliable way of indirectly accessing . properties without hitting the [] proxy. A method call syntax for that would be fine.

 Waldemar