This page lists an extension to proxies that supports operator overloading. It is a work-in-progress and likely inconsistent in some parts.
An operator handler is an object that implements the following collection of traps. In each trap (aka method), this is bound to an underlying value proxy:
{ // unary operators neg: function() -> result // for unary - pos: function() -> result // for unary + invert: function() -> result // for unary ~, invert is python's name // additional traps test: function() -> result // return a boolean for use in conditionals // binary operators. // The 'add' trap is called when the left argument to '+' is a value proxy. // The 'radd' trap is called when the left argument is not a proxy and the right argument is. add: function(rarg) -> result // python uses __add__ radd: function(larg) -> result // python uses __radd__ ... ditto for the following binary operators: sub, rsub (-) mul, rmul (*) div, rdiv (/) mod, rmod (%) lshift, rlshift (<<) rshift, rrshift (>>) bitand, rbitand (&) bitor, rbitor (|) bitxor, rbitxor (^) // comparison operators (perhaps required to return booleans) equal, requal (for ==) compare(rarg) - returns negative for <, zero for ===, positive for > rcompare(larg) }
This operator handler has a large collection of traps. We refer to the above traps as derived traps. If a particular derived trap is not implemented, it is multiplexed onto the following smaller collection of fundamental traps:
{ // unary operators unary: function(opstring, opfn) -> result // binary operators left : function(opstring, opfn, rarg) -> result right: function(opstring, opfn, larg) -> result }
Each fundamental trap is passed the operator in two forms: as a string opstring and as a function opfn, which is either a unary or binary function that performs that operation on its arguments. Thus the default implementation of the derived trap mul dispatches to the left trap as follows:
function mul (rarg) { return this.left( "*", function (l,r) { return l * r }, rarg ) }
An error is raised (which?) an operator is applied to a proxy for which neither a fundamental or derived trap is defined.
MarkM says: a TypeError, since that’s the error a normal method missing (({}).foo()) turns into.
There are eight comparison operators ==, !=, ===, !==, <, <=, >, >=. These are all required to return booleans. (This helps optimizations and understandability.)
There is no trap for !=, it is handled by the == trap equal. There is no requal trap; it is also handled by equal.
The remaining 6 comparison operators are all handled by compare. There is no need for an rcompare trap; it is also handled by compare.
Note that we could have a larger collection of traps for comparison operators that could allow more general behaviors (eg a non-symmetric == operator).
Note that both == and === on value proxies must be defined via traps.
In contrast, value proxies cannot override the proposed ‘egal’ function. On value proxies, ‘egal’ should check for equality of the (immutable) components of the value proxy (perhaps recursively, if those components are in turn value proxies, but there is no need for an expensive cycle check).
MarkM asks: Why is there no need for a cycle check? Is it impossible to build an infinite rational value?
A value proxy needs to combine:
complex.abs() etc. (May be an object proxy).typeof queries. (It should be one of the existing typeof answers, excluding “function”.)
Since these four kinds of information is likely common to many value proxies, we pre-allocate them in a ValueType object, and then reference that object from multiple value proxies.
var proxyValueType = Proxy.createValueType(operatorhandler, proto, typeofString, { x:float64, y:float64} ); var proxy = Proxy.createValue(proxyValueType, 2.0, 3.0); // example uses assert proxy.x == 2.0 proxy.abs() // calls proto.abs() with this bound to proxy proxy + 1 // calls operatorhandler.add(1) typeof(proxy) // returns typeofstring
The last argument to createValueType uses block types from the binary data strawman. What block type should we use for an arbitrary JS value?
typeof return? Perhaps one of the followingfunction (proposed above)objectObject.isPrimitive(val) method. This method (or equivalent functionality) is required as part of this proposal.proxy.left(...)), which seems problematic.call, getr, setr. This proposal adds unary, left, right, test (called truth above) and leaves pending geti and seti.