Improving ECMAScript as a compilation target

Brendan Eich brendan at mozilla.com
Tue May 5 23:15:47 PDT 2009


On May 5, 2009, at 6:53 PM, Allen Wirfs-Brock wrote:

>> Still does not connote "non-existent".
>
> If all these handlers are only invoked in missing property  
> situations then we can probably get away with an implicitly non- 
> existent connotation.

The ones that want to be called for existent properties in order to  
maintain an arraylike 'length' seem to be set and delete. For delete,  
only for identifiers that are indexes (ToUint32(id) === id), which  
could be a helpful fact, or a temptation to make distinctions on  
identifiers that we should resist.


>>> 	delete	delete a non-existent property
>>
>> Sure (hard to care about this case), but the ability to use catchalls
>> to emulate arrays may be an important use-case for virtualizing
>> systems. If delete can't be handled for existent properties then
>> length can't be updated.
>>
>
> So, maybe delete falls into a different category of handlers  
> (perhaps along with invoke and construct on the object itself,  
> defineProperty, etc...) that aren't about non-existent properties  
> but are hooks on important object level meta operations (at MOPish  
> step  but it keeps the catch all story cleaner).

'delete obj.prop' or 'delete obj[id]' needs to be caught and handled  
by some catchall associated with obj, not the existing property value  
of obj.prop or obj[id]. Again the arraylike case  leads the way.

Invoking an object via obj(), constructing via new obj -- these cases  
indeed lack a .prop or [id] to catch. They would want a lower-level MOP.

But obj.prop() is worth a catchall, this is the popular  
__noSuchMethod__ case, so that one does not have to create a method  
for each value of 'prop' and retain it in obj under that name. The  
call can be forwarded without any proxy or cloned method.


>>> 	getValue	called on any implicit or explicit non-existent
>> property
>>> access to that is not one of the above
>>
>> What is an implicit property access other than the above? I added
>> "has" because property lookup does not getValue, and must not, but
>> unless you ignore catchalls on the global object and objects named by
>> with statement heads, you need "has" too.
>>
> I hadn't thought through "implicit" but I was at least thinking  
> about accesses from built-ins. I would also expect that getValue  
> (and the others) would need to work on implicit accesses to the  
> global object or a with object.  With lookup might be trickly but I  
> think it could be probably be make to work at least in the  
> specification.  It might be necessary to make DefaultAction  
> contextual so that it does something different when trying to  
> resolve a with binding

You can't getValue speculatively, the hook might have effects.

The ES4 thinking was not far off, if you squint past namespaces, from  
what you're proposing, provided you add 'has' or 'hasProperty' to the  
suite:

http://bugs.ecmascript.org/ticket/214

These catchalls (invoke as __noSuchMethod__ is absent from this  
ticket, but it fits the same mold as the others) run only when there's  
no "own" property being acted upon.

Lars's comment 5 (http://bugs.ecmascript.org/ticket/214#comment:5) is  
worth a read. The logic leading up to "Ergo no property managed by the  
catchalls is ReadOnly" is still compelling given the practical  
obstacles to defining ReadOnly properties on prototypes. Sure, with  
ES5 you can make such non-configurable (else why bother?) constants on  
a prototype, or make an object containing such constants be the  
prototype of another object, but then [[CanPut]] comes into play. Best  
to leave prototype-chasing to the catchall implementors, we agreed --  
and so did this old ES4 ticket.

So apart from arraylikes (see below), I agree we should call catchalls  
only for missing "own" property accesses.


>>> At most one of the above is called on any property reference, and
>>> only if the property does not exist.
>>
>> Besides making it impossible to virtualize arraylikes, this will make
>> virtual DOMs hard to implement efficiently.
> Is there anything other than the delete hander that is needed to  
> emulate arrays?  What is the DOM problem?

An arraylike would want set to monitor obj[31] = "foo" where  
obj.length was < 32 (implying obj[31] did not exist before the  
assignment), and similarly delete obj[31] should retract length when  
obj[31] *does* exist.

And (here's a case where set would be wanted on an existing property)  
obj.length = 30 should retract further. But why couldn't 'length' have  
a custom setter, instead of trying to run a 'set' catchall on every  
set just to intercept 'length' updates and extend or truncate?

DOM nodelists are "live" -- they are cursors into the shared DOM tree  
and mutations can affect their contents. But they're also arraylike in  
that they have .length and can be indexed. These could be implemented  
(at some performance cost) by never creating "own" properties, instead  
always searching the DOM, caching to optimize for no mutation as needed.

So, no need for catchalls on existing direct properties of the target  
object, except for 'delete'. Is 'delete' the oddball catchall which  
should be called for existing as well as missing direct properties?


>> So perhaps invoke could be for non-callable (including non-existent,
>> i.e., imputed-undefined-value) properties.
>>
> I have some similar concerns about assignments to readonly  
> properties and perhaps other error conditions.  Maybe they are error  
> handlers.

If only accessing a non-existent property were an error (in non-strict  
mode)!

E4X would want something beyond the no-direct-property condition to  
call the hook. The "out" for now would be the same drill as for DOM  
nodelists: never populate direct properties, always compute results  
via catchalls. I like it, it slows E4X down as a deprecation device, a  
warning against using it :-P. (Not really kidding...)


> On a complete different plane, here is a wacky idea that integrates  
> this work into Object.defineProperty:
>  Object.defineProperty(obj, undefined, {invoke: function(id, args)  
> {...}, ...});
>
> Keeps the API surface smaller and  emphasizes that this is really an  
> way to define the handling of otherwise undefined properties.

It's worth considering, but a little obfuscated (or just obscure --  
undefined as id might be taken to convert to "undefined"). I don't  
mind a separate defineCatchAll addition to Object, given the  
successful additions in ES5. What do you say?

The other benefit of defineCatchAll is its atomic (re-)configuration  
of catchalls. This is an implementation win where catchalls involve  
switching a poor-man's vptr in the instance, or some similar identity- 
preserving aggregation operation.

/be



More information about the es-discuss mailing list