jason.orendorff at gmail.com
Thu Oct 15 09:57:26 PDT 2009
Operator overloading was discussed here before:
Mark Miller's strawman proposal (double dispatch)
Christian Plesner Hansen's "symmetric" operator overloading
So far discussion has focused on how to dispatch operations, but there is an
independent design dimension: exactly what behavior programs should be able to
overload. Below I advance some use cases as proposed design goals.
A motivating goal is to support defining numeric types that work in most places
where primitive numbers work. So if I write classes for Complex and Decimal, it
should be possible for me to arrange that:
Ordinary arithmetic just works, and mixing Complex numbers with primitive
numbers produces a Complex result;
Decimal("0.2") * Decimal("0.8") ---> Decimal("0.16")
5 * Complex(0, 1) --> Complex(0, 5)
Adding a number to a string invokes toString (ideally it wouldn't require
any code to get this behavior; it would be the default even for types that
overload + in other cases);
Decimal("0.2") + " m/s" ---> "0.2 m/s"
"" + Complex(0, 3) ---> "0+3i"
The increment/decrement and compound assignment operators just work, if the
underlying operator is overloaded (+ and * in these examples);
x = Decimal("1"), ++x ---> Decimal("2")
Decimal("3")++ ---> ***ReferenceError
x = Complex(3, 4); x *= Complex(0, 1) --> Complex(-4, 3)
Decimal("-1.27") <= 0 ---> true
0 < Decimal("-1.27") ---> false
Decimal("-1.27") >= Decimal("-1") ---> false
Comparisons that don't make sense throw an exception (actually this is
*not* how primitive numbers behave, but it seems like desirable behavior,
and easy enough to support for authors who want it);
Complex(0, 2) < 2 ---> ***Error
Complex(0, 2) < Complex(2, 0) ---> ***Error
Zero values are false, not true;
!Complex(0, 0) ---> true
Complex(0, 0) ? "t" : "f" ---> "f"
if (x) f(); ---> f is called if x is nonzero
User-defined numbers can be == to primitive numbers (but not ===);
Decimal("100") == 100 ---> true
100 == Decimal("100") ---> true
Complex(100, 0) == 100 ---> true
Two numbers with the same value and the same type are equal (and I should
not have to e.g. cache the instances and make Complex(5, 3) return the same
object every time).
Complex(5, 3) == Complex(5, 3) ---> true
Complex(5, 3) === Complex(5, 3) ---> true
[3, Complex(3, 0)].indexOf(Complex(3, 0)) ---> 1
I expect this bit about === will be contentious, but having indexOf return -1
here would be a real shame, and if there's a hash-map type in ES-next the case
gets even stronger. Anyway there seems little use in having === distinguish
between objects that nothing else in the language can tell apart.
Beyond numbers, there are other important use cases for operator overloading:
operator. Overloading indexing, even just for integer indices, would be
more than welcome. There are some nontrivial design decisions here, because
the language interacts with properties in so many ways:
obj[i] = x
i in obj
for (i in obj) ...
and I'm sure I left some out. Worst-case, we add a hook for each of them.
I suspect only integer indices should be supported in the language, but
WebAPI/HTML5 might benefit if ES-next defines some kind of extension point
(short of host objects) that can be used to describe the legacy Web APIs
that do catchall indexing for arbitrary property names.
operator(). SpiderMonkey supports this for host objects of custom classes
implemented in C/C++. Python supports it in the language, and the feature
is widely used, e.g. in classes that model mathematical functions, in
libraries that create wrappers/proxies for objects and their methods, and
occasionally just so that some object quacks like a function (that is, it
can be passed where a function is wanted, e.g. to an API like
addEventListener, without requiring the user to write a lambda every time).
It makes sense to support overloading both `F(x)` and `new F(x)` syntax.
Several built-in functions behave differently depending on which syntax is
used; ES programs might want to do the same.
This use case is less important, but easy enough to support, if it's considered
Mutable types. Allowing operators to apply to mutable types, and in the
case of operators such as += and ++, allowing them to modify an object in
place, has some uses. C++ supports this, but the reasons it makes sense in
C++ don't really apply to ECMAScript; Python is the only reference-language
I know of that supports it, and it is useful for cases where copying would
be costly, such as:
A *= 2; // double each element of a matrix
A += B; // add two large matrices
Python uses operators for data structures more than ES currently does,
so += is used for extending a list in-place, and |= for adding to a
In addition I'll suggest a few non-goals:
Letting programs define arbitrary sequences of punctuation
characters to be operators (for example, ** or ~= or ->).
Overloading the comma operator.
Separately overloading !x, x&&, x||, x?:, if(x), while(x), etc. (Allowing
types to override ToBoolean should be enough.)
Overloading instanceof or typeof.
Overloading the relational operators to return anything other than a
Overloading == and != separately. (A single "equals()" hook should
Overloading < <= > >= separately. (A single "compareTo()" hook should
Oddly enough, almost all of these things I call unnecessary are things several
languages do. So clearly this is subject to some discussion to say the least!
It would be possible to write a proposal in near-spec-ready language that
addresses this kind of question without assuming anything in particular about
the dispatch mechanism (the text would refer to a DispatchOperation abstract
operation) or the syntax for defining operator overloads. I'd be willing to
attempt that, in the interest of having a concrete proposal on the table.
Comments are welcome.
More information about the es-discuss