Catch-all proposal based on proxies
Mark S. Miller
erights at google.com
Wed Dec 9 21:48:59 PST 2009
On Wed, Dec 9, 2009 at 11:02 AM, Brendan Eich <brendan at mozilla.com> wrote:
> On Dec 7, 2009, at 4:11 PM, Tom Van Cutsem wrote:
> Dear all,
> Over the past few weeks, MarkM and myself have been working on a proposal
> for catch-alls for ES-Harmony based on proxies. I just uploaded a strawman
> proposal to the wiki:
> Hi Tom, great to see this proposal. I took the liberty of making a few
> small edits; hope they're ok. I like the stratification and the ab-initio
> nature of the design -- the last seems to me to be a crucial improvement
> over past proposals, which may help overcome the "climbing the meta ladder"
> Some initial comments, pruned to avoid restating others' comments:
> 1. This proposal obligates the catch-all implementor to delegate to any
> prototype object in has and get, to include unshadowed prototype properties
> in enumerate, to shadow if p in receiver.[[Prototype]] in put, and to do
> nothing for delete proxy.p if !proxy.hasOwnProperty(p).
> In general, handler writers have to implement standard prototype-based
> delegation if it is desired. This is probably the right thing, but I wonder
> if you considered the alternative where prototype delegation is handled "by
> the spec" or "by the runtime" and the proxy is considered "flat"?
We did think about it, but it seemed needlessly less flexible. If such
flat-and-delegate handling is desired, an abstraction can be built on top of
ours that emulates it as a convenience. The reverse emulation seems
difficult at best.
> 2. The fix handler returning undefined instead of throwing explicitly to
> reject a freeze, etc., attempt is a bit implicit. Falling off the end of the
> function due to a forgetten or bungled return will do this. Ok, let's say
> the programmer will test and fix the bug.
> But more significant: could there be a useful default denoted by returning
> undefined or falling off the end of the fix function? An alternative
> interpretation would be an empty frozen object. This has symmetry with
> undefined passed (or no actual argument supplied) to Object.create. It's a
> minor comment for sure.
> Since the undefined may be the result of a bug as you say, it seems worse
for the bug to silently result in fixing the proxy into an empty frozen
object. We think the current "noisy" behavior better supports defensive
> 3. Mozilla's wrappers (proxies, membranes), which we pioneered for security
> purposes (e.g. for DOM inspectors where privileged JS is interacting with
> web content objects) and which have been copied in other browsers (at least
> WebKit), implement === by unwrapping, so two wrappers for the same object
> are === with that object, and with each other.
> In answer, <
preserves === correspondence on each side of a membrane. Now that we have a
concrete catchall proposal adequate to build membranes, we'd like to restart
our discussions with Mozilla (JetPack, etc) about whether you could rebuild
some of your C++ membranes in JS code using these primitives. We should
follow up offlist.
> The proxies proposal does not have an unwrapped object, although super? is
> similar. Later in the proposal, you write "meta-level code will ‘see’ the
> proxy rather than the object it represents." This sounds more like wrappers
> as we use them -- there is always a wrapped object and its proxy or wrapper.
> The alternative of not trapping === is a leaky abstraction that inevitably
> breaks some programmers' expectations. Our early wrappers did not hook ===,
> but eventually we settled on the unwrap-before-=== behavior based on
> This is a use-case I wanted to bring to your attention (Mike Samuel raised
> it in his reply by suggesting a Proxy.proxies predicate; his [[Class]]
> question also gets to the broader issue of transparency vs. leaky proxy
> abstractions). Our wrapper experience suggests allowing === to be hooked in
> a constrained way, for certain kinds of proxies. It could be that this
> use-case can't be served by a standardized, general proxy/catch-all
> proposal, and must be done under the hood and outside of the ES spec.
To avoid some "climbing meta ladder" issues, we purposely distinguish
between what we consider base-level operations, such as x.foo, and
meta-level operations, such as Object.getOwnProperty(x, 'foo'). We attempt
to be as fully transparent (leak free) as reasonably possible at
virtualizing base level operations. We attempt to be fully non-transparent
(leak like a firehose) to meta-level operations. Some of our classification
may seem weird: Object.prototype.toString() is meta-level. It can be used to
reveal that an object is a trapping proxy. Object.getOwnPropertyNames() is
meta-level. Object.keys() is base level.
The properties of === that we feel need to be preserved:
1) "x === y" does not cause any user code to run.
2) "x === y" neither gives x access to y nor vice versa.
3) "typeof x !== 'number' && x === y" mean that x is operationally identical
to y in all observable ways. Substituting the value for x with the value of
y cannot change the meaning of a computation.
4) "x === y" implies that the Grant Matcher <
http://erights.org/elib/equality/grant-matcher/> may safely send the money
to either x or y.
A wrapper is not identical to the object it is wrapping, or there wouldn't
be any point in wrapping it. Thus, they can't be ===.
Two independent wrappers on the same object may behave differently,
depending on their definers. Thus they can't be ===.
Except for two proxies with identical parts, such as two object proxies with
identical handlers and supers. However, as shown by our example membrane,
one can just use an Ephemeron table to avoid creating semantically identical
duplicate proxies, preserving === without magic.
> 4. The [[Get]] versus [[Invoke]] rationale: indeed performance is a
> concern, but existing engines also specialize callee-computation distinctly
> from get-value, in order to optimize away Reference types. The ES specs so
> far do not, instead using the internal Reference type to delay GetValue so
> as to bind |this| to the Reference base when computing a callee and its
> receiver as part of evaluating a call expression.
> I think it is an open question whether a future spec, especially one using
> a definitional interpreter, will stick to References. If we end up making
> the distinction that all practical implementations already make, between
> get-as-part-of-callee-computation and all other get-value "gets", then I
> don't think this rationale is so strong.
> In general over-coupling to ES5 may not help either a new Harmony-era
> proposal to "get in", or to be as complete or expressive as it should be. So
> a rationale based on choices or limitations of ES1-5 seems weak to me.
I would love to see the concept of References disappear, and to see the
([[Get]], [[Call]]) pairs in the spec that really mean "call this as a
method" be rewritten as [[Invoke]]s. In that case, I would enthusiastically
agree that this catchall proposal should be upgraded with an invoke() trap.
Note how this would make our membrane code simpler and more efficient.
Rather than a get() trap at the choke point that creates and returns a
function, we'd simply have an invoke() trap whose body is that function's
I'd like to understand better how we could get rid of References.
> Thanks again for this proposal,
> You're welcome. It was fun!
-------------- next part --------------
An HTML attachment was scrubbed...
More information about the es-discuss