Gilad Bracha had a fun post about the become primitive in Smalltalk. "foo become bar" means anything that pointed to what foo points to now points to what bar pointed to and vice versa. The comments on the post are pretty good too.
Is `become' as powerful as reference-based around advice on functions? Yes:
function adviseAround (aFunc, advice) {
var swapMeIn = function () {
advice(swapMeIn, arguments);
};
aFunc become swapMeIn;
}
adviseAround(XMLHttpRequest, alert);
How about the other direction? Can we implement become (on functions) using adviseAround? Ignoring a lot of noise cases (exceptions, returns, etc.), and hoping I got the interleaving right:
function become(a, b) {
var goRaw = false;
function swap (base, advice) {
adviseAround(base, function (raw) {
if (goRaw) { raw(arguments); }
else { goRaw = true; advice(arguments); goRaw = false; }
});
}
swap(a, b);
swap(b, a);
}
become(XMLHttpRequest, alert);
At least for function objects, leaving the rest as an exercise, 'become' and 'around' are the same.
Ironically, Gilad comments "AOP. I have never made it a secret that I don't believe in that stuff." Given the above, that's an odd stance. However, he also writes "Security is another concern: containing this magic power to suspends the natural laws of the universe in a capability is crucial." Perhaps our restriction to references is what he means, distinguishing it from typical advice that can touch pretty much anything in principle. It's not an obvious approach -- for example, one of my co-workers decided to break function encapsulation due to his use cases -- but as we were interested in security, I thought it was key that we took the capability perspective.
Anyways, neato.
Hi Leo,
ReplyDeleteThis is most intriguing. What about ordinary (non-function) objects? Unless you can extend our result to them, than it seems that become: is more general - it can be used for any object.
Implementing advice in terms of become: is straightforward; the inverse is rather tricky (does that code actually work?). This again leads me to believe that become: is the better primitive to around.
Shouldn't this be:
ReplyDeletefunction become(a, b) {
var goRaw = false;
function swap (base, advice) {
adviseAround(base, function (raw) {
if (goRaw) {
goRaw = !goRaw;
raw(arguments);
goRaw = !goRaw;
}
else { goRaw = !goRaw;
advice(arguments);
goRaw = !goRaw;
}
});
}
swap(a, b);
swap(b, a);
}
Otherwise, it won't behave like become: for recursive or mutually recursive functions. But it's too late for me to think about this ...
Right, I suspected that as well.
ReplyDeleteFor extending it to objects, there is advice for interacting with fields of an object. Typically, languages support dynamically defined setters/getters for an object, etc., but missing (in JS, at least) is the ability to catch undefined field/method lookups, deletions, property lookups, etc.
To get 'become' for objects, we'd need all of those (swapping where they go, just as we did for function calls). You're right: it gets even hairier to extend 'become' to arbitrary values. However, getting the above individual pieces of functionality using just 'become' also seems tricky. Trickiness doesn't say too much about expressiveness, though.
In terms of usability... I'd want to rethink some use cases. Common security patterns, like checking arguments/context and proceeding, are easy in both. 'become' might be closer to a notion of whitelisting than blacklisting: you put in something you understand, rather than modify something you don't fully.
As for testing... my old modified interpreter is locked away in Microsoft land. After some layout engine hacking this week, I want to modify Narcissus for browser-based demo of JS + secure views to make it easy for people to play with these ideas.
ReplyDelete(a view is a context-sensitive aspect, and by secure view, I mean specializing context to be script origin and using a default-deny aspect for all views, separating the notion of passing a reference from enabling a capability)