Operator overloading

Jason Orendorff 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)
  https://mail.mozilla.org/pipermail/es-discuss/2009-January/008535.html

  Christian Plesner Hansen's "symmetric" operator overloading
  https://mail.mozilla.org/pipermail/es-discuss/2009-June/009603.html

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)

    Comparisons work;

        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]
        obj[i] = x
        delete obj[i]
        i in obj
        for (i in obj) ...
        Object.getOwnPropertyNames(obj)
        Object.getOwnPropertyDescriptor(obj, i)
        Object.defineProperty(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
worthwhile:

    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
    hash-set.

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
    boolean.

    Overloading == and != separately. (A single "equals()" hook should
    suffice.)

    Overloading < <= > >= separately. (A single "compareTo()" hook should
    suffice.)

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.

-j


More information about the es-discuss mailing list