Good, I think we're converging on what the important sub-problem is: building an abstraction mechanism that enables the open-ended expression of projections, but not the expression of non-projections. My previous statements about join were premised on such a projection-only abstraction. But I mistakenly thought that we could build it by adapting the E and Joe-E auditor logic. Sam is right that we can't. Some of Cormac's work on trace-monitoring temporal contracts might be the answer, or at least a place to start.
Once we have such an open-ended projection-only abstraction, then all these questions about equality become interesting and pressing. On Wed, Dec 4, 2013 at 1:55 PM, Peter Thiemann < thiem...@informatik.uni-freiburg.de> wrote: > On 12/4/13 5:54 PM, Mark Miller wrote: > >> >> Consider >> var p = new Proxy ( target, handler); >> The trick is *not* to trap equality, but to let the handler >> control whether EQ dereferences p to target before comparing >> pointers. Notice that one handler only controls one step of >> dereferencing. If target is a proxy, again, then EQ recursively >> asks that proxy's handler until either a non-proxy object is >> reached *or* the handler does not request further forwarding. >> Here is an outline of the proposal: >> http://proglang.informatik.uni-freiburg.de/projects/JSProxy/proxy.pdf >> >> With this design each individual proxy can choose to be >> transparent or opaque, while guaranteeing that EQ implements a >> non-trivial equivalence relation - the latter is straightforward >> to check. >> >> >> Ok good, I see how this provides a meaningful equivalence relation. But I >> don't see how it helps your goal. >> >> a) In order to do a membrane of any sort, including a contract-membrane, >> you need to use the shadow target technique, where the system-recognized >> target for master and slave in your example are distinct shadow targets. >> And where the shared real target is not system recognized, but is only our >> way of describing what the handlers are doing; and thus not available to >> any system enforcement mechanism. >> > > I don't think that all contracts require a membrane, for example, if you > just restrict an object to reading or writing a subset of its properties, > then no recursive wrapping is required. > I don't understand "shadow target technique", but if the master/slave > roles are contracts like Role1 and Role2, then each of them can be > represented by a proxy and the two proxies can share a single target. > > > >> You write "If x===y, then uses of x and y *in their common interface* >> (i.e., in the intersection of their roles) are interchangeable." >> >> b) But this is untrue in your proposal, even if the same >> system-recognized target were somehow used, because the different handlers >> can perform different side effects, including differing side effects on the >> shared target. >> > > True. I was stuck in the contract world, where the handler implements a > projection. > So I should tune down my proposal to contract wrappers where contracts are > implemented in a suitable DSL that only expresses projections. > > > >> >> Let's distinguish two categories of membrane: >> >> x) A general membrane maintains the basic membrane invariant, that there >> is no leakage across the membrane boundary. But invocations that cross the >> membrane can have their meaning changed by the handlers in any way allowed >> by the JS Proxy invariant enforcement mechanism. >> >> y) A guarding membrane is one which either disallows an operation, or >> (identity aside) allows it with the same semantics it would have had in the >> absence of the membrane's interposition. >> >> Given checkably-pure functions (which E and Joe-E have but JS currently >> does not) one can use the general membrane mechanism to build a membrane >> maker to be parameterized by checkably-pure guard predicates, so that it >> only makes guarding membranes. What you (and IIUC Phil and Jeremy) are >> generally seeking are guarding membranes, which would answer issue #b. But >> in JS (as of ES6) these would be implemented by unprivileged user code, and >> so not something that the === implementation could directly recognize. A >> new equivalence operation, whether an EQ-like equivalence predicate or a >> join, could be provided as part of the same package and be in bed with this >> guarding membrane maker. >> >> Note that the guarding membranes made by this maker would still need to >> use the shadow target technique, so objection #a still stands -- testing >> "the same system-recognized proxy target" will not help you build these new >> equivalence operations as part of the guarding membrane maker package. >> > > My understanding of the above is that given an appropriate way of > restricting a handler to essentially implement a projection, then the proxy > of that handler could safely be transparent while granting an equivalence > restricted to uses in the common interface. > Well, such a handler DSL could be designed and then we could all have our > cake and eat it. > > > -Peter > > > >> >> >> >> In any case, weakening EQ so that the only guarantee that >> remains is that it is an equivalence relation would break >> existing uses of EQ in JS (i.e., "===" aside from NaN). For >> example, an EQ that always said true would still be an >> equivalence relation, so clearly something more is needed. The >> position Tom and I take is that, aside from -0.0, JS === >> guarantees full observational equivalence, i.e., for any >> program containing the fragment >> >> if (x === y) { >> .... x .... >> } >> >> if all or any or all of the mentions of x in the body were >> changed to y, the meaning of the program would not change. >> (Also, given the usual simplifying assumptions about renaming, >> non-shadowing, and alpha equivalence.) >> >> If the guarantees your === would provide is weaker than that, >> what are its guarantees, and why do you believe those to be >> sufficient? >> >> >> Observational equivalence is hard to argue against, but I’ll give >> it a try, anyway. I believe such a strong spec is too limiting and >> excludes many useful applications. Examples that I am concerned >> about are wrappers for transparent monitoring and enforcing >> contracts. Apparently and unsurprisingly others are concerned >> about these examples, too. >> >> >> Here is an example scenario. Then, I provide some code fragments >> to support a proposal for a weaker spec for ===. This leads to a >> question about join in the end. >> >> ~~~~~~ >> >> Consider a function sync(master,slave) that takes two objects and >> that wants these two objects to exchange information: the master >> syncing with the slave. For efficiency, the function sync first >> checks if these two objects are “the same”, in which case no >> synchronization is needed; perhaps a flag or timestamp needs to be >> set to indicate that synchronization has been achieved. If the >> objects are different, then some potentially expensive sequence of >> method calls may be needed to exchange the information. (Perhaps >> involving authentication, passing of capabilities, etc.) >> >> >> If we put a contract on sync that imposes a Master interface on >> master and a Slave interface on slave and this contract is >> implemented using opaque proxies, then the sync function is losing >> the efficient shortcut. >> >> >> (Why would an object implement the Master and the Slave interface? >> Here are two possible reasons: the master may be elected at run >> time or there may be a hierarchical system where level-n masters >> are slaves on level-(n+1)) >> >> ~~~~~~ >> >> A proposal for a weaker spec for === >> >> >> In the following code fragments, I am using Java interface >> notation to indicate a dynamically checked contract for brevity. >> >> Let’s say we have a function that can treat “same” objects more >> efficiently, as motivated in the sync example: >> >> >> function f (x, y) { >> >> ... >> >> if (x === y) { >> >> … x.foo(); x.inc(); y.inc(); z = y.bar() … >> >> } >> >> … >> >> } >> >> >> Using contracts, x and y may be restricted to certain roles. >> >> Let’s say, >> >> interface Role1 { >> >> foo(); >> >> inc(); >> >> } >> >> interface Role2 { >> >> bar(); >> >> inc(); >> >> } >> >> >> var g = … apply contract( (Role1, Role2) -> Any ) to f; >> >> >> var obj = { >> >> state: 42; >> >> foo: function () { this.state = 0; }; >> >> bar: function () { return this.state;}; >> >> inc: function () { this.state++; }; >> >> } >> >> >> g (obj, obj); >> >> >> I want to argue that this call of g should execute the code >> guarded by (x === y) in f, because the interposition of the >> contract may otherwise change the semantics. In other words, I >> want to make sure that g(obj, obj) either raises an error due to a >> contract violation or it has exactly the same effect and result as >> f(obj, obj). >> >> >> What guarantees can we assume below (x===y)? >> >> If x===y, then uses of x and y *in their common interface* (i.e., >> in the intersection of their roles) are interchangeable. >> >> >> In the example, the common interface is >> >> interface Role1&Role2 { >> >> inc(); >> >> } >> >> therefore >> >> if (x === y) { … x.foo(); x.inc(); y.inc(); z = y.bar() … } >> >> must be observationally equivalent to each of the following >> >> if (x === y) { … x.foo(); x.inc(); x.inc(); z = y.bar() … } >> >> if (x === y) { … x.foo(); y.inc(); y.inc(); z = y.bar() … } >> >> if (x === y) { … x.foo(); y.inc(); x.inc(); z = y.bar() … } >> >> >> That is, sameness of x and y is reduced to the ability to observe >> the same state changes through both x and y. Of course, limited to >> the common interface. >> >> >> The extreme case for the above f-example would arise if the >> interfaces Role1 and Role2 were disjoint. In this case, no >> transformation would be sanctioned from the knowledge that x === y. >> >> ~~~~~~ >> >> This observation leads me to a question about join: >> >> If only join is provided, but not EQ, then I don’t see how to >> implement the function f above. If I successfully obtain z = >> join(x, y), then it fulfills interface Role1&Role2, but I cannot >> use it in lieu of x or y in the body of the conditional because it >> does not implement foo() and bar(). >> >> If Role1&Role2 were empty, then the successful join would produce >> a useless object. For example, in the Master/Slave example above, >> it is highly likely that the interfaces are disjoint. >> >> >> >> Given a synchronous join: >> >> if (join(master, slave)) { >> ... use master for mastery stuff, use slave for slavy stuff ... >> } else { >> ... do the more expensive thing ... >> } >> >> Given the actual async join: >> >> Q(join(master, slave)).then(() => { >> ... use master for mastery stuff, use slave for slavy stuff ... >> }, () => { >> ... do the more expensive thing ... >> }); >> >> We're here using join only to test whether it succeeds, not to actually >> use its result. Admittedly this is kludgy, but IMO no more kludgy than the >> problem statement demands. This would certainly not be the typical case for >> using join. >> >> >> >> >> -- Cheers, >> --MarkM >> >> >> -Peter >> >> >> >> >> >> -- >> Text by me above is hereby placed in the public domain >> >> Cheers, >> --MarkM >> > > -- Text by me above is hereby placed in the public domain Cheers, --MarkM _______________________________________________ dev-tech-js-engine-internals mailing list dev-tech-js-engine-internals@lists.mozilla.org https://lists.mozilla.org/listinfo/dev-tech-js-engine-internals