OK, the example would most likely create a bad recursion; it should better be:
MyClass.prototype['+'] = function(arg, reverse) {
return reverse ? String(arg) + " plus " + this.toString() : this.toString() +
" plus " + String(arg);
}
I hope, though, that the meaning is clear enough :)
Michael
From: [email protected] [mailto:[email protected]] On
Behalf Of Michael Daumling
Sent: Friday, January 09, 2009 2:35 AM
To: Mark S. Miller; es-discuss
Subject: RE: Operator Overloading Strawman for Harmony
Hi Mark,
This is very close to the pattern that Adobe's ExtendScript already offers, and
that has been rejected before, unfortunately. The differences are:
1) There is no "reverse+" (or other construct). Instead, ExtendScript has
a second argument "reverse" that is a Boolean, and is true if the operator is
reverse.
2) For unary operators, the argument is undefined.
3) If the function returns undefined, it invokes the default behavior of
the operator.
Example (very brief), assuming that toString() returns "5":
...
MyClass.prototype['+'] = function(arg, reverse) {
return reverse ? arg + " plus " + this : this + " plus " + arg;
}
var obj = new MyClass (5);
+obj; // prints "5 plus undefined"
obj + 10; // prints "5 plus 10"
10 + obj; // prints "10 plus 5"
Michael
From: [email protected] [mailto:[email protected]] On
Behalf Of Mark S. Miller
Sent: Thursday, January 08, 2009 9:13 PM
To: es-discuss
Subject: Operator Overloading Strawman for Harmony
First, let me acknowledge that there's been a long history of attempts to add
operator overloading to EcmaScript, that these have all died for various
reasons leaving much documentation in its wake, and that I have not read this
documentation at the present time. If I am simply rehashing ideas which are
already well refuted, my apologies for wasting everyone's time. But I did
verbally discuss these ideas with Waldemar, who has lived through this history,
and he thought this was plausible enough to be worth proposing.
Why raise this difficult issue again? We some may remember from the decimal
wars, my motivation in this is to enable library designers to create new
numeric types -- like rational, complex, quaternions, vector, matrix, surreal
numbers, whatever -- so that not every numeric type need come from the
EcmaScript committee. If we do our job as language designers -- of providing
adequate abstraction mechanisms -- then library authors can use these to build
a diversity of abstractions. For efficiency reasons, some scalar numeric types,
like integers and decimal, may still be provided primitively. But once we have
a framework such that these *could* be provided as a library, then adopting
them as directly supported primitives becomes merely an optimization. (Indeed,
efficiency considerations in the presence of multicore may demand that vectors
and matricies be primitives, but let's not worry about that yet.)
At the request of the rest of the committee, one of Sam's early decimal
attempts did exactly this. It defined a decimal API equivalent to one that
could be provided as a library on ES3.1. ES3.1 has no operator overloading
whatsoever, so we thought we could do this and be downwards compatible from a
future language 9ES4 at the time) which would allow the operators to be used
directly on decimal values. These plans ran aground on "+", which clarifies
what problems need to be solved.
Strawman proposal
Define a new nominal type named, let's say, "Operable". (I'm not stuck on this
name. Perhaps "Numeric"?) We don't yet know what nominal type system Harmony
will have, so for concreteness, and to separate issues, let's use "instanceof"
as our nominal type test, where only functions whose "prototype" property is
frozen can be treated as types. (Otherwise, instanceof is not monotonic.) Let's
say that Operable is ES3.1-like function acting as an purely abstract class (or
in Java, a marker interface). Operable throws if called (hey, it's abstract),
it's prototype property is frozen (non-writable, non-enumerable,
non-configurable), and points at an empty object that inherits from
Object.prototype. We redefine the original Number.prototype so that it inherits
from Operable.prototype, so all numbers are Operable. Crucially, strings remain
non-Operable. (I'm dangerously ignoring here the distinction between primitives
and wrappers. In fact, in ES3 and ES3.1, "e instanceof Number" is false. Fixing
the strawman accordingly presents no problems but makes it tedious. I will
continue ignoring this for now.)
We are concerned only about the "normal" operators, which exclude at least &&,
||, ?:, ++, --, ., or ===. For each of the normal operators, we insert the
following tests at the beginning of their definition after all operands are
evaluated and GetValue()d but before they are ToPrimitive()d. For example, for
infix "+" at 11.6.1 The Addition Operator in the ES3.1 spec, after step 4,
taking a few self-hosting notational shorthands:
Let Left = Result(2)
Let Right = Result(4)
If isOperable(Left) and isOperable(Right) then
harmony code: return Left['+'](Right)
// else fall through to the current behavior.
To make this work, for each of the operators that operate on numbers, we'd add
a corresponding built-in method to the original Number.prototype. In
self-hosting style:
Number.prototype['+'] = function(arg) {
if (isNumber(arg)) {
return primAdd(this, arg);
} else {
return arg['reverse+'](this);
}
};
Number.prototype['reverse+'] = function(receiver) {
if (isNumber(receiver)) {
return primAdd(receiver, this);
} else {
throw ...;
}
};
This trick is adapted from Smalltalk, which did something similar. Newer
numeric abstraction understand the build in ones and some number of widely
adopted older ones. But the older ones don't understand the new ones. If X is a
primitive Number and Y and Z are user-defined Complex numbers, then
Y + Z // fine, since complex Y knows what to do with complex Z
Y + X // fine, since complex Y knows what to do with number X
X + Y // number X doesn't know what to do with Y, so it asks Y to handle it by
// Y['reverse+'](X), which Y can successfully handle.
What if we compose together two new numeric types, say Rational and Complex,
where each was defined in ignorance of the other. Say R is a Rational.
Y + R // Y is clueless, so it asks R: R['reverse+'](Y)
// R is clueless, so it gives up rather than re-reversing
This distinction between "+" and "reverse+" also handles the non-commutativity
of some operators.
An elaboration suggested by Mike Samuel:
Rather than have Operable.prototype be empty, since one expects various
operators to have algebratic relationships to each other, we can install
default methods defining some operators in terms of a smaller set than new
numerics will still need to override. For example
Operable.prototype['+'] = function(arg) { return this - -arg; };
A related issue: Should various internal methods have similar tests for
Operable, and invoke the operable to handle this. Waldemar raised coercion to
boolean. Perhaps ToBoolean(foo), if foo is an Operable, should invoke foo['!!']
or something. We'd then install default methods for these in Operable.prototype.
Mike also raised the issue of whether we should split Operable (+,-,*,/,...) vs
Comparable (<,<=,==,>=,>). I find that plausible, given that we have a nominal
type system that can handle non-tree subtype relationships, i.e., something
other than instanceof.
So, is there some reason why this won't work?
--
Cheers,
--MarkM
_______________________________________________
Es-discuss mailing list
[email protected]
https://mail.mozilla.org/listinfo/es-discuss