My ECMAScript 7 wishlist

# Nicholas C. Zakas (10 years ago)

I wrote this blog post about some of the pain points I'm dealing with and dreams of how ES7 might be able to address them:

www.nczonline.net/blog/2014/06/03/my-ecmascript-7-wishlist

A short overview in lieu of posting the whole article here:

  • Array.prototype.first(), Array.prototype.last() - return the first and last items, respectively.
  • Array.prototype.isEmpty() - return true when empty (would also be nice on strings, maps, etc.).
  • Function.empty - a standard empty function that can be used when you just want an empty function (IMHO, it indicates intent much better than other options toda).
  • Custom descriptor attributes - David mentioned this likely will never happen, which makes me sad. Maybe the decorators proposal solves this use case.
  • Object.deepPreventExtensions(), Object.deepSeal(), Object.deepFreeze() - deep versions of Object.preventExtensions(), et al.
  • Object.preventUndeclaredGet() - change an object's behavior to throw an error if you try to read from a property that doesn't exist (instead of returning undefine).
  • Lightweight traits - simple syntax sugar for object literals and classes to facilitate mixins.

Further rationale and explanation is in the post. The last three, in particular, scratch particular itches I currently have.

Note: Please don't take these as formal proposals. If any of the ideas seems worthwhile, I'm happy to discuss further and/or put together an actual proposal.

Thanks.

# Rick Waldron (10 years ago)

On Thu, Jun 5, 2014 at 6:42 PM, Nicholas C. Zakas < standards at nczconsulting.com> wrote:

I wrote this blog post about some of the pain points I'm dealing with and dreams of how ES7 might be able to address them:

www.nczonline.net/blog/2014/06/03/my-ecmascript-7-wishlist

A short overview in lieu of posting the whole article here:

  • Array.prototype.first(), Array.prototype.last() - return the first and last items, respectively.

Check out Till's response to the previous thread about first and last: esdiscuss/2014-May/037158

  • Array.prototype.isEmpty() - return true when empty (would also be nice on strings, maps, etc.).

+1

  • Function.empty - a standard empty function that can be used when you just want an empty function (IMHO, it indicates intent much better than other options toda).

+1

  • Custom descriptor attributes - David mentioned this likely will never happen, which makes me sad. Maybe the decorators proposal solves this use case.

The Proxy security issue: esdiscuss.org/topic/object-getownpropertydescriptor-can-return-just-about-anything I had hoped that a "black list" of writable, configurable, enumerable properties would suffice: esdiscuss.org/topic/object-getownpropertydescriptor-can-return-just-about-anything#content-14 but I there is a discussion around some form of custom meta properties.

  • Object.deepPreventExtensions(), Object.deepSeal(), Object.deepFreeze() - deep versions of Object.preventExtensions(), et al.

Does "deep" mean that a Map instance's [[MapData]] is frozen if deepFreeze is called on a ? eg. what happens here:

var m = Object.deepFreeze(new Map());
m.set(1, 1);

In your blog it mentions the silent failure in non-strict mode, I suspect that would still have to apply to these additions for semantic consistency.

  • Object.preventUndeclaredGet() - change an object's behavior to throw an error if you try to read from a property that doesn't exist (instead of returning undefine).

This can be achieved with Proxy right, or is that too cumbersome?

  • Lightweight traits - simple syntax sugar for object literals and classes to facilitate mixins.

+1

# Axel Rauschmayer (10 years ago)
  • Function.empty - a standard empty function that can be used when you just want an empty function (IMHO, it indicates intent much better than other options toda).

Ironically, that’s what Function.prototype is. But using that object in that capacity is beyond confusing. I’d prefer a different name such as “noop” or “doNothing”; “empty” doesn’t feel right in the context of something executable.

Also, I don’t find using an empty arrow function too bad (to me, it looks quite intention-revealing):

someAsyncMethod(() => {});
# Brendan Eich (10 years ago)

Rick Waldron wrote:

  • Object.preventUndeclaredGet() - change an object's behavior to throw an error if you try to read from a property that doesn't exist (instead of returning undefine).

This can be achieved with Proxy right, or is that too cumbersome?

js> var NoSuchProperty = Proxy({}, {
   has: function(target, name) { return true; },
   get: function(target, name, receiver) {
     if (name in Object.prototype) {
       return Object.prototype[name];
     }
     throw new TypeError(name + " is not a defined property");
   }
});
js> var obj = Object.create(NoSuchProperty)

js> obj.foo = 42

42
js> obj.foo

42
js> obj.bar
/tmp/p.js:7:4 TypeError: bar is not a defined property
# Allen Wirfs-Brock (10 years ago)

On Jun 5, 2014, at 5:52 PM, Brendan Eich <brendan at mozilla.org> wrote:

Rick Waldron wrote:

  • Object.preventUndeclaredGet() - change an object's behavior to throw an error if you try to read from a property that doesn't exist (instead of returning undefine).

This can be achieved with Proxy right, or is that too cumbersome?

js> var NoSuchProperty = Proxy({}, { has: function(target, name) { return true; }, get: function(target, name, receiver) { if (name in Object.prototype) { return Object.prototype[name]; } throw new TypeError(name + " is not a defined property"); }

need to make sure accessor methods use the right this value:

get: function (target, name, receiver) {
       if (name in Object.prototype) return Reflect.get(Object.prototype, name, receiver);
       throw new TypeError(name + “ is not a defined property”);
 }
# Brendan Eich (10 years ago)

LOL - as if O.p has getters!

Thanks, this is more general in case of insanity.

# Allen Wirfs-Brock (10 years ago)

On Jun 5, 2014, at 6:25 PM, Brendan Eich <brendan at mozilla.org> wrote:

LOL - as if O.p has getters!

Thanks, this is more general in case of insanity.

It probably always a good idea to use Reflect.* calls inside of proxy handlers. I’m seen variants of this mistake in many handlers snippets that are floating around. Need to encourage best practices.

# David Bruant (10 years ago)

Le 06/06/2014 01:08, Rick Waldron a écrit :

On Thu, Jun 5, 2014 at 6:42 PM, Nicholas C. Zakas <standards at nczconsulting.com <mailto:standards at nczconsulting.com>> wrote:

* `Object.deepPreventExtensions()`, `Object.deepSeal()`,
`Object.deepFreeze()` - deep versions of
`Object.preventExtensions()`, et al.

Does "deep" mean that a Map instance's [[MapData]] is frozen if deepFreeze is called on a ? eg. what happens here:

var m = Object.deepFreeze(new Map()); m.set(1, 1);

I think the intention behind Object.freeze was to make objects immutable (at a shallow level), so maybe the semantics of Map.prototype.set (and all modifying operations, of Map&co) should be changed to read the [[IsExtensible]] and throw if false is returned. Given Maps are already in the wild, this decision might need to be taken quickly.

or should an Object.makeImmutable be introduced? (it would be freeze + make all internal [[*Data]] objects immutable)

* `Object.preventUndeclaredGet()` - change an object's behavior to
throw an error if you try to read from a property that doesn't
exist (instead of returning `undefine`).

(I already know that Nicholas and I disagree on the topic, but sharing for debating).

This can be achieved with Proxy right, or is that too cumbersome?

Code-readability-wise, wrapping in a proxy is as cumbersome as a call to Object.preventUndeclaredGet I guess.

This sort of concerns are only development-time concerns and I believe the runtime shouldn't be bothered with these (I'm aware it already is in various web). For instance, the TypeScript compiler is capable today of catching this error. Given that we have free, cross-platform and fairly easy to use tools, do we need assistance from the runtime?

David

[1] twitter.com/passy/status/469127322072014849

# Mark S. Miller (10 years ago)

On Fri, Jun 6, 2014 at 5:44 AM, David Bruant <bruant.d at gmail.com> wrote:

Le 06/06/2014 01:08, Rick Waldron a écrit :

On Thu, Jun 5, 2014 at 6:42 PM, Nicholas C. Zakas < standards at nczconsulting.com> wrote:

  • Object.deepPreventExtensions(), Object.deepSeal(),

Object.deepFreeze() - deep versions of Object.preventExtensions(), et al.

Does "deep" mean that a Map instance's [[MapData]] is frozen if deepFreeze is called on a ? eg. what happens here:

var m = Object.deepFreeze(new Map()); m.set(1, 1);

I think the intention behind Object.freeze was to make objects immutable (at a shallow level), so maybe the semantics of Map.prototype.set (and all modifying operations, of Map&co) should be changed to read the [[IsExtensible]] and throw if false is returned. Given Maps are already in the wild, this decision might need to be taken quickly.

The main intention of Object.freeze is to make objects tamper proof. To ensure that their API surface is only according to the provider, not to the clients, and thereby prevent client A from corrupting the API seen by client B. This is why Object.freeze has no effect on non-configurable accessor properties -- their writable-property-like behavior is fully in control of the abstraction provider.

Of course, it does also cause some further immutability beyond that initial purpose, and so there become valid purposes too. The API surface of an array consists of data properties, so freezing an array turns it into a shallowly immutable array. Typed Arrays refuse to be frozen as of ES6. Post ES6, perhaps we will enable them to be frozen, resulting in their indexed properties being immutable, since these are presented as data properties. Were we allow these to be frozen, they would also become un-neuterable, which means they would be passed over postMessage by sharing of the underlying data, rather than by ownership transfer of that data, which is exactly what you want once the data is immutable.

By contrast, a Map's state is more like the private instance variable state of a closure or a post-ES6 class. Object.freeze of a Map should not alter the mutability of this state for the same reason it does not alter the state captured by a closure or a future class instance.

or should an Object.makeImmutable be introduced? (it would be freeze + make all internal [[*Data]] objects immutable)

We do need something like that. But it's a bit tricky. A client of an object should not be able to attack it by preemptively deep-freezing it against its wishes.

  • Object.preventUndeclaredGet() - change an object's behavior to throw an error if you try to read from a property that doesn't exist (instead of returning undefine).

(I already know that Nicholas and I disagree on the topic, but sharing for debating).

Once again, perhaps it is something that the provider of an object could determine, but it shouldn't be something up to an object's clients. But like you observe below, we can already do this with a proxy, so let's not introduce a second mechanism without a compelling reason.

And the proxy solution wraps the initial object, rather than changing its behavior in place, so it does not enable one client to corrupt the behavior seen by other clients.

This can be achieved with Proxy right, or is that too cumbersome?

Code-readability-wise, wrapping in a proxy is as cumbersome as a call to Object.preventUndeclaredGet I guess.

This sort of concerns are only development-time concerns and I believe the runtime shouldn't be bothered with these (I'm aware it already is in various web). For instance, the TypeScript compiler is capable today of catching this error. Given that we have free, cross-platform and fairly easy to use tools, do we need assistance from the runtime?

Yes. Object.freeze is a runtime production protection mechanism, because attacks that are only prevented during development don't matter very much ;).

# David Bruant (10 years ago)

Le 06/06/2014 15:57, Mark S. Miller a écrit :

By contrast, a Map's state is more like the private instance variable state of a closure or a post-ES6 class.

The capabilities to arbitrarily modify Maps (set/delete on all keys, with any values) will be expected by any ES6-compliant code to be globally available, so a Map's state cannot reasonably be considered private. This differs from the state of a closure where its access is strictly moderated by the public API giving access to it and to the fact that this API is not provided globally (unlike Map.prototype).

Object.freeze of a Map should not alter the mutability of this state for the same reason it does not alter the state captured by a closure or a future class instance.

I'd argue the Map state is very much like regular objects (for which you can't deny [[Set]], [[Delete]], etc.), not closure's state.

In an ES6 world, denying access to the global Map.prototype.* would break legitimate code, so that's not really an option confiners like Caja could provide.

or should an Object.makeImmutable be introduced? (it would be
freeze + make all internal [[*Data]] objects immutable)

We do need something like that. But it's a bit tricky. A client of an object should not be able to attack it by preemptively deep-freezing it against its wishes.

I don't see the difference with shallow-freezing? It's currently not possible to defend against shallow-freezing (it will be possible via wrapping in a proxy).

This can be achieved with Proxy right, or is that too cumbersome?
Code-readability-wise, wrapping in a proxy is as cumbersome as a
call to Object.preventUndeclaredGet I guess.

This sort of concerns are only development-time concerns and I
believe the runtime shouldn't be bothered with these (I'm aware it
already is in various web). For instance, the TypeScript compiler
is capable today of catching this error. Given that we have free,
cross-platform and fairly easy to use tools, do we need assistance
from the runtime?

Yes. Object.freeze is a runtime production protection mechanism, because attacks that are only prevented during development don't matter very much ;).

Just to clarify, I agree that Object.freeze was necessary in ES5 (have we had proxies, it might have been harder to justify?), because there was no good alternative to protect an object against the parties it was shared with. But the concern Nicholas raises doesn't seem to have this property. Reading a property that doesn't exist doesn't carry a security risk, does it? Object.preventUndeclaredGet doesn't really protect against anything like ES5 methods did.

# Mark S. Miller (10 years ago)

On Fri, Jun 6, 2014 at 7:37 AM, David Bruant <bruant.d at gmail.com> wrote:

Le 06/06/2014 15:57, Mark S. Miller a écrit :

By contrast, a Map's state is more like the private instance variable state of a closure or a post-ES6 class.

The capabilities to arbitrarily modify Maps (set/delete on all keys, with any values) will be expected by any ES6-compliant code to be globally available, so a Map's state cannot reasonably be considered private. This differs from the state of a closure where its access is strictly moderated by the public API giving access to it and to the fact that this API is not provided globally (unlike Map.prototype).

That's a good point, but doesn't really address what I'm trying to say. The map's state is manipulable through its API surface, rather than being the properties which are its API surface. In the abstract perhaps this is a subtle distinction, but it is the distinction Object.freeze is built on.

Consider if you wrote in JavaScript a Map abstraction of your own that held the data structures of your map implementation in private instance variables. Object.freeze would clearly not touch your map implementation. For builtins, internal properties serve the role that private instance variables will in class instances.

This does bring up an interesting issue though. Even if you wanted your map to respond to Object.freeze by making this internal implementation be no longer mutable, you can't, unless the Map is a proxy, because no other JS object can sense when it is being frozen. More on this in another email coming soon.

Object.freeze of a Map should not alter the mutability of this state for

the same reason it does not alter the state captured by a closure or a future class instance.

I'd argue the Map state is very much like regular objects (for which you can't deny [[Set]], [[Delete]], etc.), not closure's state.

In an ES6 world, denying access to the global Map.prototype.* would break legitimate code, so that's not really an option confiners like Caja could provide.

Why would we want to deny access to Map.prototype.* ? If there were a Map among the primordials, for example, if Map.prototype itself were a Map, then, in combination with the primordial existence of Map.prototype.set, we'd have a global communications channel. (Indeed, SES repairs Date.prototype when Data.prototype is a Date, and likewise for WeakMap < code.google.com/p/google-caja/source/browse/trunk/src/com/google/caja/ses/repairES5.js#2806> bugzilla.mozilla.org/show_bug.cgi?id=861219.)

or should an Object.makeImmutable be introduced? (it would be freeze + make all internal [[*Data]] objects immutable)

We do need something like that. But it's a bit tricky. A client of an object should not be able to attack it by preemptively deep-freezing it against its wishes.

I don't see the difference with shallow-freezing? It's currently not possible to defend against shallow-freezing (it will be possible via wrapping in a proxy).

Therefore a defensible API surface in JS cannot contain as part of its contract that it presents data properties that remain writable. Except as you point out, for proxies which refuse to be frozen. This restriction on the possibilities for defensible API surfaces is an unfortunate consequence of the unpleasant choices we had for securing ES5, as you point out below.

 This can be achieved with Proxy right, or is that too cumbersome?

Code-readability-wise, wrapping in a proxy is as cumbersome as a call to Object.preventUndeclaredGet I guess.

This sort of concerns are only development-time concerns and I believe the runtime shouldn't be bothered with these (I'm aware it already is in various web). For instance, the TypeScript compiler is capable today of catching this error. Given that we have free, cross-platform and fairly easy to use tools, do we need assistance from the runtime?

Yes. Object.freeze is a runtime production protection mechanism, because attacks that are only prevented during development don't matter very much ;).

Just to clarify, I agree that Object.freeze was necessary in ES5 (have we had proxies, it might have been harder to justify?), because there was no good alternative to protect an object against the parties it was shared with.

Exactly. But even with proxies, proxies are way way too heavy to be the only form of defensible object. ES6 classes also give us a place to stand to start to talk about the difference between provider and client. Hopefully ES7 will support defensible classes that make defensible instances. But because of the historical path these decisions took, I expect that this class-based defensiveness will only be sugar for a pattern of freezing anyway, so it won't make a fundamental difference.

But the concern Nicholas raises doesn't seem to have this property. Reading a property that doesn't exist doesn't carry a security risk, does it? Object.preventUndeclaredGet doesn't really protect against anything like ES5 methods did.

That's true, but misses the point I was trying to make. For normal ES objects, it is already part of their API contract with their clients that the clients can do feature testing to detect the presence or absence of a method. The most common way to do such feature testing is to get the property and see if it is falsy. (Variations include, testing for undefined, testing for undefined or null, and testing if its typeof is "function".) It's fine if the provider of an abstraction does not wish to support this pattern. But it is not ok for one client of an object which does support it to prevent that object's other clients from successfully using feature detection.

# Frankie Bagnardi (10 years ago)

Couldn't preventUndeclaredGet() be implemented with proxies?

It actually sounds like an extremely useful feature for development builds of libraries and applications. Typos are very very common, and often difficult to look over while debugging. On the other hand, it would break a lot of existing code if you try to pass it as an object to a library; you'd have to declare every possible value it might check (which isn't necessarily bad). Most of the time, it's just an options object, or an object it'll iterate over the keys of.

Using it on arrays would also reduce off-by-1 errors (though I don't see them often in JS).

# David Bruant (10 years ago)

Le 06/06/2014 17:47, Frankie Bagnardi a écrit :

Couldn't preventUndeclaredGet() be implemented with proxies?

Yes it can. Doing it left as an exercise to the reader... Wait... Don't bother, Nicholas did it :-) www.nczonline.net/blog/2014/04/22/creating-defensive-objects-with-es6-proxies

It actually sounds like an extremely useful feature for development builds of libraries and applications. Typos are very very common, and often difficult to look over while debugging. On the other hand, it would break a lot of existing code if you try to pass it as an object to a library; you'd have to declare every possible value it might check (which isn't necessarily bad). Most of the time, it's just an options object, or an object it'll iterate over the keys of.

Using it on arrays would also reduce off-by-1 errors (though I don't see them often in JS).

Ever since I've started using forEach/map/filter/reduce, I haven't had an off-by-one error on arrays. Highly recommanded! (I think I've heard Crockford making the same recommandation in a recent talk, but I cannot find the link)

# Nicholas C. Zakas (10 years ago)

On 6/5/2014 4:08 PM, Rick Waldron wrote:

* `Object.deepPreventExtensions()`, `Object.deepSeal()`,
`Object.deepFreeze()` - deep versions of
`Object.preventExtensions()`, et al.

Does "deep" mean that a Map instance's [[MapData]] is frozen if deepFreeze is called on a ? eg. what happens here:

var m = Object.deepFreeze(new Map()); m.set(1, 1);

In your blog it mentions the silent failure in non-strict mode, I suspect that would still have to apply to these additions for semantic consistency.

I wouldn't expect this to apply to [[MapData]] since those are not enumerable own properties of the map instance.

* `Object.preventUndeclaredGet()` - change an object's behavior to
throw an error if you try to read from a property that doesn't
exist (instead of returning `undefine`).

This can be achieved with Proxy right, or is that too cumbersome?

It can be done with Proxy, but that kind of sucks because I always need to go through the extra step of creating the Proxy for each object and passing around the Proxy instead. To me, this is similar to setting a property to be readonly in strict mode, except its writeonly (or rather, write-first).

# Nicholas C. Zakas (10 years ago)

On 6/6/2014 8:38 AM, Mark S. Miller wrote:

But the concern Nicholas raises doesn't seem to have this
property. Reading a property that doesn't exist doesn't carry a
security risk, does it? Object.preventUndeclaredGet doesn't really
protect against anything like ES5 methods did.

That's true, but misses the point I was trying to make. For normal ES objects, it is already part of their API contract with their clients that the clients can do feature testing to detect the presence or absence of a method. The most common way to do such feature testing is to get the property and see if it is falsy. (Variations include, testing for undefined, testing for undefined or null, and testing if its typeof is "function".) It's fine if the provider of an abstraction does not wish to support this pattern. But it is not ok for one client of an object which does support it to prevent that object's other clients from successfully using feature detection.

Sorry I was sleeping while most of this conversation was happening. :)

I understand the point about feature detection, it would suck if some random code did Object.preventUndeclaredGet() on an object you own and were using feature detection on. I still wish for some way to do this other than through proxies, but I agree that it would be nice for just the object provider to be able to set this behavior.

# David Bruant (10 years ago)

Le 06/06/2014 18:16, Nicholas C. Zakas a écrit :

On 6/6/2014 8:38 AM, Mark S. Miller wrote:

But the concern Nicholas raises doesn't seem to have this
property. Reading a property that doesn't exist doesn't carry a
security risk, does it? Object.preventUndeclaredGet doesn't
really protect against anything like ES5 methods did.

That's true, but misses the point I was trying to make. For normal ES objects, it is already part of their API contract with their clients that the clients can do feature testing to detect the presence or absence of a method. The most common way to do such feature testing is to get the property and see if it is falsy. (Variations include, testing for undefined, testing for undefined or null, and testing if its typeof is "function".) It's fine if the provider of an abstraction does not wish to support this pattern. But it is not ok for one client of an object which does support it to prevent that object's other clients from successfully using feature detection.

Sorry I was sleeping while most of this conversation was happening. :)

I understand the point about feature detection, it would suck if some random code did Object.preventUndeclaredGet() on an object you own and were using feature detection on. I still wish for some way to do this other than through proxies, but I agree that it would be nice for just the object provider to be able to set this behavior.

It is possible via an API following the same pattern than revocable proxies:

 {object, toggle} = makeUndeclGetThrowObject()
 toggle(); // now the object throws when there is a [[Get]] on an 

undef property

Keep the toggle function locally so only trusted parties access it, share the object as you wish. Admittedly more cumbersome than your solution or proxies :

# Andrea Giammarchi (10 years ago)

On Fri, Jun 6, 2014 at 8:38 AM, Mark S. Miller <erights at google.com> wrote:

The most common way to do such feature testing is to get the property and see if it is falsy.

this is why for years many of us have been advocating the usage of 'name' in object instead of passing through getters ... but I can see the problem when it comes to retrieve, let's say requestAnimationFrame, passing through all prefixes ...

About that, if I can add one wish for ES7, I really wish that if there's anything that works with a prefix, an experimentalPrefix property is exposed somewhere so that we can stop guessing which one would work for the current engine and simplify features detections too.

Best

# Brendan Eich (10 years ago)

Nicholas C. Zakas wrote:

It can be done with Proxy, but that kind of sucks because I always need to go through the extra step of creating the Proxy for each object and passing around the Proxy instead. To me, this is similar to setting a property to be readonly in strict mode, except its writeonly (or rather, write-first).

What about the code I showed, which shows a singleton being spliced high on many objects' prototype chains to handle the missing property throw?

js> var NoSuchProperty = Proxy({}, { has: function(target, name) { return true; }, get: function(target, name, receiver) { if (name in Object.prototype) { return Reflect.get(Object.prototype, name, receiver); } throw new TypeError(name + " is not a defined property"); } }); js> var obj = Object.create(NoSuchProperty)

js> obj.foo = 42

42 js> obj.foo

42 js> obj.bar /tmp/p.js:7:4 TypeError: bar is not a defined property

You could avoid Object.create by assigning to a Constructor.prototype, or hacking with proto, of course.

# Brendan Eich (10 years ago)

Just FTR, I'll put a ratioanlized ES7 proposal on the agenda for the July TC39 meeting, as per

twitter.com/BrendanEich/status/475067783282057216

Thanks to Nicholas for the suggestion!

# Andrea Giammarchi (10 years ago)

that has:()=>true breaks with features detections that are meant to be

less obtrusive than getters ... i.e. 'geolocation' in navigator or 'innerHTML' in genericNode and all others that are not supposed to pass through a potentially expensive getter to retrieve a feature detection purpose info.

Long story short that NoSuchProperty is obtrusive in an upside-down way ... I personally would not use that and would not expect that anywhere

# André Bargull (10 years ago)

that has:()=>true breaks with features detections that are meant to be less obtrusive than getters ... i.e. 'geolocation' in navigator or 'innerHTML' in genericNode and all others that are not supposed to pass through a potentially expensive getter to retrieve a feature detection purpose info.

Long story short that NoSuchProperty is obtrusive in an upside-down way ... I personally would not use that and would not expect that anywhere

The 'has' handler is required to work around a Firefox limitation. A compliant ES6 implementation only needs the 'get' handler.

Related bug reports: bugzilla.mozilla.org/show_bug.cgi?id=914314, bugzilla.mozilla.org/show_bug.cgi?id=1009199

# Andrea Giammarchi (10 years ago)

Interesting bug, thanks for the info.

In such case the only concern would be why Object.prototype is considered but not inherited properties too.

If inheritance should be considered, I'd rather go like this:

var NoSuchProperty = Proxy({}, {
  get: function(target, name, receiver) {
    while (target = Object.getPrototypeOf(target)) {
      if (name in target) {
        return Reflect.get(target, name, receiver);
      }
    }
    throw new TypeError(name + " is not a defined property");
  }
});

Best

# Brendan Eich (10 years ago)

Andrea Giammarchi wrote:

In such case the only concern would be why Object.prototype is considered but not inherited properties too.

Because NoSuchProperty is meant to be inserted just before Object.prototype, avoiding that loop.

What's more, the loop is unnecessary:

var NoSuchProperty = Proxy({}, { get: function(target, name, receiver) { while (target = Object.getPrototypeOf(target)) { if (name in target) { return Reflect.get(target, name, receiver); } } throw new TypeError(name + " is not a defined property"); } });

If NoSuchProperty is inserted just before Object.prototype on a chain of ordinary objects, its get handler won't be invoked until no such property is indeed found along the path from the original target to NoSuchProperty. Therefore all iterations but the last (where target is Object.prototype) are redundant.

# Andrea Giammarchi (10 years ago)

Because NoSuchProperty is meant to be inserted just before

Object.prototype

I miss this constrain, wouldn't have bothered with the loop otherwise.

# Brendan Eich (10 years ago)

sorry for the tardy reply. I did propose NoSuchProperty to Ecma TC39 today. To recap:

// Library code starts here.

const NoSuchProperty = Proxy(Object.prototype, {
   // uncomment for nasty Firefox bug workaround:
   // has: function(target, name) { return true; },
   get: function(target, name, receiver) {
     if (name in Object.prototype) {
       return Reflect.get(Object.prototype, name, receiver);
     }
     throw new TypeError(name + " is not a defined property");
   }
});

function NoSuchPropertyClass() {}
NoSuchPropertyClass.prototype = NoSuchProperty;

// End library code.
// Your client code starts here.

class MySaferClass extends NoSuchPropertyClass {
   ...
}

The library code is self-hosted based on ES6 Proxies and Reflect.

The committee reaction was to let this be put in popular libraries, in forms to be polished based on actual developer experience, and then we can standardize once there is a clear winner and strong adoption.

Hope this is survivable. I argued we should shortcut to reduce the burden on the ecosystem but (as I've argued many times) TC39 believes we are least capable compared to the wider ecosystem (github, etc.) in designing, user-testing, polishing, and finalizing APIs. We can do final polish and formal specification, for sure. Y'all should do the hard part, not because we are lazy but because you are many, closer to your problem domains and use-cases, and collectively wiser about the details.

# Tab Atkins Jr. (10 years ago)

This works great as a general principle, but honestly tons of languages have already forged this path. It's pretty straightforward, I think.

# Brendan Eich (10 years ago)

I made the case for building it in sooner, but TC39 wanted "less sooner" based on library usage. There was a minority position (as voiced; could be majority) that argued JS is different, object detection plus optional "fields" means this is the wrong direction.

# Brendan Eich (10 years ago)

Not trying to be controversial ;-). Would you use ES6 classes and have the ones you wrote all extend NoSuchPropertyClass?

# Tab Atkins Jr. (10 years ago)

Using subclassing to bung in some arbitrary trait is really terrible. :/ It requires either adding it up high in your class hierarchy, or having to write a custom NoSuchPropertyClass which extends your superclass, so you can then extend it.

I'm rather surprised that the group actually considered that sort of code to be appropriate - it's known that it doesn't compose well with other traits-as-superclasses or normal subclassing.

# Andrea Giammarchi (10 years ago)

Agreed with Tab, in terms of composing inherited functionality it's hard to beat prototypal and the ease of Class.prototype.__noSuchProperty__ = function(){ ... }; traitish approach.

I also think Proxy already gives us a way to work around the __noSuchProperty__ "issue"

# Brendan Eich (10 years ago)

Tab Atkins Jr. wrote:

Using subclassing to bung in some arbitrary trait is really terrible. :/ It requires either adding it up high in your class hierarchy, or having to write a custom NoSuchPropertyClass which extends your superclass, so you can then extend it.

Ok, let's not hand-wave mixin syntax, though (Andrea hacks proto). What API do you prefer?

I'm rather surprised that the group actually considered that sort of code to be appropriate - it's known that it doesn't compose well with other traits-as-superclasses or normal subclassing.

What code was not appropriate? TC39 balked at standardizing library API, and good for them. You want better API, design it!

Or did you want sealed class or other such syntax, and I misunderstood?

# Tab Atkins Jr. (10 years ago)

On Wed, Sep 24, 2014 at 3:28 PM, Brendan Eich <brendan at mozilla.org> wrote:

Tab Atkins Jr. wrote:

Using subclassing to bung in some arbitrary trait is really terrible. :/ It requires either adding it up high in your class hierarchy, or having to write a custom NoSuchPropertyClass which extends your superclass, so you can then extend it.

Ok, let's not hand-wave mixin syntax, though (Andrea hacks proto). What API do you prefer?

I'm partial to magic-named functions, but that's probably my experience speaking, rather than a more thoughtful opinion. Symbol-named magic functions would be better, since we have those and most other languages don't.

Andrea wasn't even hacking proto - they're just adding it to the class prototype, so newly constructed objects'll have it.

I'm rather surprised that the group actually considered that sort of code to be appropriate - it's known that it doesn't compose well with other traits-as-superclasses or normal subclassing.

What code was not appropriate? TC39 balked at standardizing library API, and good for them. You want better API, design it!

The use of a Proxy superclass to implement the functionality, which you demonstrated in an earlier email.

Or did you want sealed class or other such syntax, and I misunderstood?

Nah, using superclasses in general is the bad thing here; it doesn't compose well without multi-inheritance, which JS likely isn't going to do.

# Brendan Eich (10 years ago)

Tab Atkins Jr. wrote:

Ok, let's not hand-wave mixin syntax, though (Andrea hacks proto). What

API do you prefer?

I'm partial to magic-named functions, but that's probably my experience speaking, rather than a more thoughtful opinion. Symbol-named magic functions would be better, since we have those and most other languages don't.

Andrea wasn't even hacking proto - they're just adding it to the class prototype, so newly constructed objects'll have it.

Right, noSuchProperty -- I was an Andrea post behind.

How is this easier to compose than adding a proxy object on the prototype chain just before Object.prototype?

Or did you want sealed class or other such syntax, and I misunderstood?

Nah, using superclasses in general is the bad thing here; it doesn't compose well without multi-inheritance, which JS likely isn't going to do.

Adding a magic-name property in the prototype chain is topologically no different (assuming no collision on the name, and no dead-reckoning by distance along prototype chain [which is considered brittle already]) from extending the prototype chain.

Instead of asserting "bad thing" and "inappropriate", can you show where the difference between the two (magic name vs. magic prototype) matters?

# Tab Atkins Jr. (10 years ago)

On Thu, Sep 25, 2014 at 11:08 AM, Brendan Eich <brendan at mozilla.org> wrote:

Tab Atkins Jr. wrote:

Ok, let's not hand-wave mixin syntax, though (Andrea hacks proto). What

API do you prefer?

I'm partial to magic-named functions, but that's probably my experience speaking, rather than a more thoughtful opinion. Symbol-named magic functions would be better, since we have those and most other languages don't.

Andrea wasn't even hacking proto - they're just adding it to the class prototype, so newly constructed objects'll have it.

Right, noSuchProperty -- I was an Andrea post behind.

How is this easier to compose than adding a proxy object on the prototype chain just before Object.prototype?

Adding an object to the prototype chain requires you to modify your class hiearchy. If you add it at the very top (before Object.prototype), this means that you're affecting every class in the tree, even if you meant to only target a single class somewhere further down. If you try to add it in a targeted way in the middle of your hierarchy, it requires either proto hacking, something like:

class superclass {...} ... x = makeNSPProxy() x.proto = superclass() class subclass extends x {...}

With this, you lose the implicit information about the actual superclass/subclass information. The Proxy isn't actually a superclass in any semantics sense, it's just something you're shimming in because you need to hijack property lookups and the prototype chain is where we go when we can't find a property.

But of course, this doesn't work either - it'll catch properties that are defined further up the prototype chain. So you're stuck with either applying it to every class, or doing further proto hacking, to walk the object's proto chain on instantiation and install the Proxy as a top-most proto, or installing it as top-most prototype for everyone but doing type checks on instances to make sure it only fires for the classes you want.

On the other hand, a magic property can be placed exactly where you want it, without any difficulty. It can be added to individual objects with the same difficulty as adding it to an entire class, or class hierarchy. It doesn't require leaning about Proxies (a very complicated topic!) and Reflect to learn how to properly implement it by overriding the correct traps, and not accidentally over-trapping. It's a very simple way to do something that's very often desired, and easy to do in many languages.

Basically, I think the group has trapped itself in a "it's technically possible already, why do we need to do anything?" trap, which is easy for a lot of web platform people to fall into these days. :/

Or did you want sealed class or other such syntax, and I misunderstood?

Nah, using superclasses in general is the bad thing here; it doesn't compose well without multi-inheritance, which JS likely isn't going to do.

Adding a magic-name property in the prototype chain is topologically no different (assuming no collision on the name, and no dead-reckoning by distance along prototype chain [which is considered brittle already]) from extending the prototype chain.

Instead of asserting "bad thing" and "inappropriate", can you show where the difference between the two (magic name vs. magic prototype) matters?

Topological similarity and practical similarity aren't always similar, topologically or otherwise. Hopefully I've demonstrated it above.

# Brendan Eich (10 years ago)

Tab Atkins Jr. wrote:

If you try to add it in a targeted way in the middle of your hierarchy, it requires either proto hacking, something like:

class superclass {...} ... x = makeNSPProxy() x.proto = superclass() class subclass extends x {...}

(In such code, you need x to be a function with .prototype that is the proxy, as just up-thread at

esdiscuss/2014-September/039565

but no big deal.)

Yes, the proxy's get handler must deal with superclasses other than Object, if that's required.

The requirements aren't clear (we're probably assuming something that doesn't match what developers want in full), but that's the point: TC39 is not the library designer droid you're looking for.

On the other hand, a magic property can be placed exactly where you want it, without any difficulty.

No one made a coherent proposal for the magic property. It was not clear from Andrea's latest post, where he even allows

"I also think Proxy already gives us a way to work around the __noSuchProperty__ "issue""

that this wish-fulfillment noSuchProperty magic property does not have to handle superclass delegation.

So please define the magic property fully before recommending it as better than the Proxy-based library approach. How would it work? The engine calls the function value of this magic property only when the object on which it is set has no such own property?

If so, then the handler function must not throw if some superclass contains the wanted name. Just as the Proxy get handler I showed does for Object.prototype.

If not, then does the magic property trigger only when the object on which it is set and every object on its prototype chain has no such property? That is a bit nasty on the JS engine optimized lookup side, but perhaps implementors should work harder so users don't have to.

Is that what you're proposing? Then we should summon optimizing engine hackers. I've cc'ed one :-P.

# Tab Atkins Jr. (10 years ago)

On Thu, Sep 25, 2014 at 12:50 PM, Brendan Eich <brendan at mozilla.org> wrote:

Tab Atkins Jr. wrote:

If you try to add it in a targeted way in the middle of your hierarchy, it requires either proto hacking, something like:

class superclass {...} ... x = makeNSPProxy() x.proto = superclass() class subclass extends x {...}

(In such code, you need x to be a function with .prototype that is the proxy, as just up-thread at

esdiscuss/2014-September/039565

but no big deal.)

Yes, the proxy's get handler must deal with superclasses other than Object, if that's required.

The requirements aren't clear (we're probably assuming something that doesn't match what developers want in full), but that's the point: TC39 is not the library designer droid you're looking for.

On the other hand, a magic property can be placed exactly where you want it, without any difficulty.

No one made a coherent proposal for the magic property. It was not clear from Andrea's latest post, where he even allows

"I also think Proxy already gives us a way to work around the __noSuchProperty__ "issue""

that this wish-fulfillment noSuchProperty magic property does not have to handle superclass delegation.

So please define the magic property fully before recommending it as better than the Proxy-based library approach. How would it work? The engine calls the function value of this magic property only when the object on which it is set has no such own property?

If so, then the handler function must not throw if some superclass contains the wanted name. Just as the Proxy get handler I showed does for Object.prototype.

If not, then does the magic property trigger only when the object on which it is set and every object on its prototype chain has no such property? That is a bit nasty on the JS engine optimized lookup side, but perhaps implementors should work harder so users don't have to.

Is that what you're proposing? Then we should summon optimizing engine hackers. I've cc'ed one :-P.

Yes, that is what I'm proposing. If lookup fails completely (reaches Object.prototype without finding the named property), it then does a second lookup for the magic NSP property, and if it finds it, executes it with the property name, returning the return value as the result of the original lookup.

I don't understand how you inferred from Andrea's post that "this wish-fulfillment noSuchProperty magic property does not have to handle superclass delegation.". At minimum it needs to handle delegating to the object's own prototype (it would be a pretty poor NSP if it couldn't handle methodMissing use-cases as well), and I don't think there's a reasonable case to stop at just one level up; doing so would make this very fragile to refactoring your hierarchy, as methods show up as missing or not depending on where they end up in the class hierarchy.

# Boris Zbarsky (10 years ago)

On 9/25/14, 4:31 PM, Tab Atkins Jr. wrote:

Yes, that is what I'm proposing. If lookup fails completely (reaches Object.prototype without finding the named property), it then does a second lookup for the magic NSP property, and if it finds it, executes it with the property name, returning the return value as the result of the original lookup.

SpiderMonkey used to support noSuchMethod, I believe.

It was removed because it caused problems for the JIT (in addition to being non-standard, etc, etc). Might be worth looking into what those were, exactly, before we consider standardizing something like this.

# Brendan Eich (10 years ago)

On Sep 25, 2014, at 7:56 PM, Boris Zbarsky <bzbarsky at mit.edu> wrote:

SpiderMonkey used to support noSuchMethod, I believe.

I implemented noSuchMethod long ago, for the TIBET folks (Smalltalk style JS framework; they wanted a doesNotUnderstand analogue).

Please note well the difference between noSuchMethod and anything like noSuchProperty. Even ignoring JITs, back in the interpreter only days, I could justify nSM because it was called on a slow path, when the only outcome without it was a guaranteed "obj.foo is not callable" or worse, "obj.foo is not defined" error.

IOW nSM only kicked in when evaluating

obj.foo(args)

Not just

obj.foo

Any nSP of the kind we seem to be discussing would need to fail fast, on evaluation of the dot expression. That is a fast path.

# Andrea Giammarchi (10 years ago)

The only reason I've used __noSuchProperty__ name in that example was to indeed remind __noSuchMethod__ and others anti-name-clashing old patterns we all know but basically I was trying to explain that composing instead of extending is a win for these kind of "magic" behaviors.

It is possible to use a Proxy without needing to go up to Object.prototype:

// before creating instances of MyClass
// after MyClass definition ...
Object.setPrototypeOf(
  MyClass.prototype,
  withNoSuchPropertyProxy(
    MyClass.prototype
  )
);

but what if we'd like to add more than one "magic" functionality ?

I also agree with Tab that is not so trivial to add is kind of functionality the right way.

The old way ( I am not rehashing here, just explaining ) would have made everything easy for developers:

// easy ES3 shenanigans anti JIT
MyClass.prototype.__noSuchMethod__ = function (id, args) { ... };


// fast path via Symbols as ES6 likes them ...
MyClass.prototype[Symbol.noSuchProperty]= function (id) { ... };

Although using all these Symbols to simulate what traits would do in other languages does not feel right.

About TC39 simplifying dev life, well ... isn't that the purpose of most ES6 new features and sugar ?

So yes, there's some good intent and job going on here but few times I've also read something like: "it's hard for engines but there are APIs that let you do that on JS side" ... now, imagine how hard and potentially wrong or slow would be in developers hands instead ...

As summary, I don't have a solution or a proposal for noSuchProperty and I honestly don't feel the need for it (because Proxy) but I'd like to see some discussion (in another thread maybe) about lightweight traits that are also in Nicolas list (last point of his initial email)

Best

# Brendan Eich (10 years ago)

Tab Atkins Jr. wrote:

I don't understand how you inferred from Andrea's post that "this wish-fulfillment noSuchProperty magic property does not have to handle superclass delegation.".

I did not infer that from Andrea's post as his position -- rather the reverse, because he said "I also think Proxy already gives us a way ...", to wit the code I showed earlier. Hence my confusion about what was being proposed that differed.

At minimum it needs to handle delegating to the object's own prototype (it would be a pretty poor NSP if it couldn't handle methodMissing use-cases as well), and I don't think there's a reasonable case to stop at just one level up; doing so would make this very fragile to refactoring your hierarchy, as methods show up as missing or not depending on where they end up in the class hierarchy.

Methinks you protest too much. Just put it below Object.prototype and get on with life. That was the idea, anyway.

Yes, what you propose is more flexible. Also more costly. Good luck selling implementors! I hope Andreas will comment now.

# Dean Landolt (10 years ago)

On Fri, Sep 26, 2014 at 10:29 AM, Brendan Eich <brendan at mozilla.org> wrote:

Tab Atkins Jr. wrote:

I don't understand how you inferred from Andrea's post that "this wish-fulfillment noSuchProperty magic property does not have to handle superclass delegation.".

I did not infer that from Andrea's post as his position -- rather the reverse, because he said "I also think Proxy already gives us a way ...", to wit the code I showed earlier. Hence my confusion about what was being proposed that differed.

At minimum it needs to handle

delegating to the object's own prototype (it would be a pretty poor NSP if it couldn't handle methodMissing use-cases as well), and I don't think there's a reasonable case to stop at just one level up; doing so would make this very fragile to refactoring your hierarchy, as methods show up as missing or not depending on where they end up in the class hierarchy.

Methinks you protest too much. Just put it below Object.prototype and get on with life. That was the idea, anyway.

Yes, what you propose is more flexible. Also more costly. Good luck selling implementors! I hope Andreas will comment now.

Out of curiosity, wouldn't Object.observe require implementors to add precisely this kind of hook into the vm anyway?

# Jason Orendorff (10 years ago)

On Thu, Sep 25, 2014 at 6:56 PM, Boris Zbarsky <bzbarsky at mit.edu> wrote:

SpiderMonkey used to support noSuchMethod, I believe.

It's still there.

js> var anObject = { noSuchMethod() { return "what"; } };

js> anObject.saysWhat(); "what"

We did tweak the semantics last year (a minor improvement, I think): bugzilla.mozilla.org/show_bug.cgi?id=916949

# Brendan Eich (10 years ago)

Dean Landolt wrote:

Out of curiosity, wouldn't Object.observe require implementors to add precisely this kind of hook into the vm anyway?

No, but O.o has its own costs. See

lists.w3.org/Archives/Public/public-script-coord/2014JulSep/0204.html

# Anne van Kesteren (10 years ago)

On Fri, Sep 26, 2014 at 5:01 PM, Jason Orendorff <jason.orendorff at gmail.com> wrote:

On Thu, Sep 25, 2014 at 6:56 PM, Boris Zbarsky <bzbarsky at mit.edu> wrote:

SpiderMonkey used to support noSuchMethod, I believe.

It's still there.

js> var anObject = { noSuchMethod() { return "what"; } }; js> anObject.saysWhat(); "what"

bugzilla.mozilla.org/show_bug.cgi?id=683218 is the bug on getting it removed, in case anyone is interested.

# Brendan Eich (10 years ago)

Brendan Eich wrote:

Dean Landolt wrote:

Out of curiosity, wouldn't Object.observe require implementors to add precisely this kind of hook into the vm anyway?

No, but O.o has its own costs. See

lists.w3.org/Archives/Public/public-script-coord/2014JulSep/0204.html

To say a bit more, as Andreas wrote at the link above, O.o has multifarious costs todo with mutation and notification.

An unstratified and general purpose noSuchProperty trap would impose costs on property sets as well as gets. Why "as well as"? Because of the "override mistake" where a prototype non-writable data property prevents assignment of a shadowing property; and where (symmetric to the data property case) a prototype accessor's setter must be called. If nSP is defined to do other than throw, e.g., to reify a property on demand from some peer property-space, then it must be called for both set and get in the missing case.

You could define nSP (maybe someone will, it's still tedious to guess here :-/) to apply only to get, but assuming it would work as its name and interface suggest -- for more than merely throwing an error when called -- I'm proceeding as if it must be called (if present) for missing in-not-own property on set as well as get.

The costs should accrue only if some object on the object at hand or along its prototype chain has nSP set on it, but that doesn't help much for implementors. Splitting out prototype chain walking for all gets and sets imposes high cost of code duplication and specialization in modern engines. So-called inline caches must be forked to handle both cases, if you want good performance for objects "below" the one with nSP.

Andreas can say more, but this general problem of pervasive costs and complications due to unstratified traps is exactly the reason we put metaprogramming APIs on Proxies, mostly (legacy accessors, good old o[x] for computed name x, and a few others aside).

(BTW, complaining about complexity of Proxy and Reflect is off topic, a move-the-goalposts attempt that makes me grumpy. Library code hides the details. The issue we're trying to get to is user-facing functionality, not implementation complexity inside the black box the user faces.)

When I say "TC39 wanted to let the ecosystem handle this" it was first and foremost about not rushing library design by committee, or even by champions, into a spec, when the the greater number of developers could do something better, search multiple paths in the design space, cooperate and compete, and meet the demand.

But there was also a desire not to jam more unstratified traps into objects, with their optimized hot paths. That's why proxies were added. If they can't meet the main use case that Nicholas had in mind, we should find out the hard way, from real code, not just from advance speculations.

If the main use case is to emulate (for one's own object-based abstractions) Python and other languages that do not allow obj.typo to pass without runtime error, then a library to add a proxy just below Object.prototype and above one's prototypal (constructor <=> class)

hierarchy might be enough. It won't satisfy all possible use-cases, but that's a positive if it hits the main target and spares us unstratified traps hitting hot paths.

# Dean Landolt (10 years ago)

On Fri, Sep 26, 2014 at 11:10 AM, Brendan Eich <brendan at mozilla.org> wrote:

Dean Landolt wrote:

Out of curiosity, wouldn't Object.observe require implementors to add precisely this kind of hook into the vm anyway?

No, but O.o has its own costs. See

lists.w3.org/Archives/Public/public-script-coord 2014JulSep/0204.html

Sure, O.o isn't free, and I get that using @noSuchProperty would likely result in all kinds of deoptimization. But of all the costs listed in that thread, I'm not seeing any mention of the cost of intercepting changes for notification queueing. I'm assuming this has to be part of any O.o implementation (IIUC the only alternative would be polling observed objects every turn). More importantly that must already paid for all observed objects. You made the comment "Good luck selling implementors", and I was just wondering aloud whether they've already been sold on doing the heavy lifting for the sake of O.o.

So to clarify: if this particular functionality is likely to be baked into vms at some point in the future, ISTM this is a much stronger argument for the kind of magic property Tab's advocating for. It could be supported efficiently at the platform level, no Proxy baggage necessary. This wouldn't be impossible in library code.

# Brendan Eich (10 years ago)

Dean Landolt wrote:

http://lists.w3.org/Archives/Public/public-script-coord/2014JulSep/0204.html

Sure, O.o isn't free, and I get that using @noSuchProperty would likely result in all kinds of deoptimization. But of all the costs listed in that thread, I'm not seeing any mention of the cost of intercepting changes for notification queueing.

You may have missed these points, then:

"""

Finally, just to clear up some myths, observation is nothing close to free inside V8 either:

  • Observing an object slows down all its mutations severely, easily by 10x-100x, depending on the case.

  • It invalidates a range of routine optimisations, especially for bulk mutations like on arrays. Essentially, observing an object forces every potentially impure operation on it onto the slowest path.

  • Observation introduces additional type polymorphism, which can cause even unrelated optimisations to fall off the cliff.

Most of these costs are inherent to the mechanism, and there probably isn't much of a chance that they can be optimised by more than constant factors.

/Andreas

"""

I'm assuming this has to be part of any O.o implementation (IIUC the only alternative would be polling observed objects every turn). More importantly that must already paid for all observed objects. You made the comment "Good luck selling implementors", and I was just wondering aloud whether they've already been sold on doing the heavy lifting for the sake of O.o.

See my longer followup. A general-purpose (as its name implies) nSP would hit lookup (get as well as set) paths, not just mutation (set). But it seems Andreas's "just to clear up some myths" words were missed, even considering only mutation (not notification).

# Brendan Eich (10 years ago)

Brendan Eich wrote:

A general-purpose (as its name implies) nSP would hit lookup (get as well as set) paths, not just mutation (set). But it seems Andreas's "just to clear up some myths" words were missed, even considering only mutation (not notification).

From private correspondence with Dean, it seems the cost of O.o (bad enough that the web platform cannot use it all over) may be assumed to be no better and not much worse than the cost of nSP. This isn't the case, nSP is strictly costlier and more complicated than O.o.

In JS, set applies after prototype chain walking (to find the setter, or the non-writable data property [in which case error], or to decide to make an own property on the directly referenced object). Call the proto walk step lookup.

get is more common than set but both require lookup, with differences.

O.o hits set but nSP hits lookup so affects both reading and writing.

A narrow throw-only nSP, say throwOnNoSuchProperty with boolean value, would hit only the get-driven lookup paths. Still costlier in dynamic frequency and static code complexity than O.o, but much more targeted and (I think) optimizable. Need Andreas Rossberg and others to weigh in.

The "pay only if you use nSP or O.o" argument has limits. O.o got into Harmony on that basis, but then the myth that it was cheap enough to use all over the web platform grew -- bad myth, Andreas busted it.

A general nSP risks similar myth or hype-cycle and won't be optimized enough to "stick", any more than O.o will, per Andreas's final "Most of these costs are inherent to the mechanism, and there probably isn't much of a chance that they can be optimised by more than constant factors".

This implementation complexity and perf cost issue is not just about implementor burden. It changes naive user-(myth)understood economics and adds attractive nuisance risk. But just considering VM costs (code and runtime), it's a big deal.

A Proxy on a prototype chain, in contrast, modularizes the hit. It deoptimizes without multiplying lookup paths by two. The cost of host objects and proxies on prototypes has already been sunk, since you could make such chains even in 18 years ago.

I don't like it when implementor vs. user trade-offs fight in what seems like a zero-sum game. I'm Captain Kirk, I don't believe in the no-win scenario. (How'd that end? Ignore the retread/remake with role reversal!)

But in this case both implementors and users are facing physics. If the root post in this thread wants only an error for obj.typo, we shouldn't be chasing nSP, which is unstratified and over-general. We should try the proxy-in-library-clothing path and if it is too painful, consider some targeted and optimizable fix.

# Tab Atkins Jr. (10 years ago)

On Thu, Sep 25, 2014 at 5:38 PM, Brendan Eich <brendan at mozilla.org> wrote:

On Sep 25, 2014, at 7:56 PM, Boris Zbarsky <bzbarsky at mit.edu> wrote:

SpiderMonkey used to support noSuchMethod, I believe.

I implemented noSuchMethod long ago, for the TIBET folks (Smalltalk style JS framework; they wanted a doesNotUnderstand analogue).

Please note well the difference between noSuchMethod and anything like noSuchProperty. Even ignoring JITs, back in the interpreter only days, I could justify nSM because it was called on a slow path, when the only outcome without it was a guaranteed "obj.foo is not callable" or worse, "obj.foo is not defined" error.

IOW nSM only kicked in when evaluating

obj.foo(args)

Not just

obj.foo

Any nSP of the kind we seem to be discussing would need to fail fast, on evaluation of the dot expression. That is a fast path.

I, personally, have only ever used Python and PHP's nSP functionality to implement methods. Most of the fancy uses I see for it in, say, Ruby (like the cool dynamic query methods on ORMs), are also methods. There are certainly uses for this kind of functionality for non-method properties, but I suspect just going with noSuchMethod would satisfy most use-cases. I'd certainly be happier with that. ^_^

# Brendan Eich (10 years ago)

Tab Atkins Jr. wrote:

Any nSP of the kind we seem to be discussing would need to fail fast, on evaluation of the dot expression. That is a fast path.

I, personally, have only ever used Python and PHP's nSP functionality to implement methods. Most of the fancy uses I see for it in, say, Ruby (like the cool dynamic query methods on ORMs), are also methods. There are certainly uses for this kind of functionality for non-method properties, but I suspect just going with noSuchMethod would satisfy most use-cases. I'd certainly be happier with that. ^_^

noSuchMethod is not what Nicholas sketched on his wishlist, though.

There are two valid use-cases/proposals:

  1. Smalltalk doesNotUnderstand (but Smalltalk makes everything a method selected by a message, no get vs. invoke [get+call] distinction), which led to SpiderMonkey's noSuchMethod. This is an unstratified trap to handle obj.foo() but not obj.foo -- in JS, whether it handles do { let f = obj.foo; f(); } is a good question.

  2. Nicholas's request for a way to make obj.typo an error. I wish I had a nickel for every time someone new to JS asked me for this since 1995 (or perhaps 1998 when try-catch was done). This is all about failing fast on typo'ed or otherwise missing property references, gets as well as get+calls.

These are two distinct things. You should start a new thread if you want (1). But we've been over it several times already:

www.google.com/search?q=site%3Aesdiscuss.org+noSuchMethod

What new helium do you have this time to get noSuchMethod airborne? The Proxy-based library solution from Tom Van Cutsem at

esdiscuss.org/topic/nosuchmethod-and-direct-proxies

is sucking heavier gasses out of the air around you :-P.