Operator overloading revisited

Brendan Eich brendan at mozilla.com
Tue Jun 30 22:26:53 PDT 2009


On Jun 30, 2009, at 1:04 PM, Mark S. Miller wrote:

> On Mon, Jun 29, 2009 at 10:26 PM, Brendan Eich <brendan at mozilla.com>  
> wrote:
> On Jun 29, 2009, at 11:55 AM, Mark S. Miller wrote:
>>
>>  Let's try a reductio ad absurdum.
>
> This doesn't settle anything since there is no "for all" claim in  
> Chambers, et al.'s words cited above. You can't reduce to an  
> absurdity something that according to its own proponents does not  
> apply in all cases.
>
> A fine counterargument if you can distinguish which cases it does  
> and doesn't apply to. The syntactic distinction "operators" seems  
> silly. I'm glad you seem to include Math.atan2(), as that gets us  
> beyond the silly syntactic distinction.

Did you check out any of the papers I linked? They talk about non- 
silly, not-only-about-syntax use-cases (~20% by function population, I  
recall Chambers writing somewhere) that include (dyadic) operators,  
math functions, event sourcing/sinking, a few other multi-party  
situations with symmetric relations and no obvious primary receiver.

I don't think there was ever a silly syntactic distinction about  
operators in anything I've written, recently or in past years, on this  
list or elsewhere. Value types want operator and literal  
extensibility. The operator part is one of N use-cases for  
multimethods IMHO. That's all.


>> It seems to me you are the one making a universal claim, for OOP  
>> methods as the one true way, which could be argued to lead to  
>> absurd (or just awkward) conclusions such as double dispatch (hard  
>> for V8, easy for TraceMonkey :-P) and "reverse+", "reverse-", etc.  
>> operator-method bloat.
>
> Contradicted by the next text of mine you quote:

But not by your double-dispatch proposal for operators. That's one way  
to add extensible operators. The other is to add multimethods. I don't  
know of a distinct and canonical third way.

Talking about good old functions doesn't help rule out multimethods,  
indeed it raises them as an option since dyadic operators appear to be  
functions, not receiver-targeted methods.

To be honest I wasn't sure what your point about good old bar(y, z)  
was, except to say that bar was the locus of responsibility (Tucker  
noted this callee-is-responsible case too). Again this does not argue  
against multimethods.

Likewise blame systems are great (costly, PLT Scheme users typically  
add contracts at module boundaries to reduce overhead, intra-module  
calls can be fast at the cost of losing the blame-carrying wrappers),  
but I don't see how they require w or bar to bear singular  
responsibility.

Multimethods really are multiple functions inhabiting bar, dispatched  
by a "most specific argument type" matching algorithm, which can fail  
but when it succeeds, invokes a single function. That's the  
responsible function, whatever bar multimethod it is. ;-)


> I was not arguing that w.foo(x) is always better than bar(y,z).  
> Rather, I was saying that currently neither violate locality of  
> responsibility. In these two expressions, w and bar are the  
> respective responsible parties.

Ok, that's what I thought. Now consider y % z where % is defined as in  
Christian's proposal, amended by Allen's suggestion that the spec not  
rely on non-internal, prototype-delegated properties. I'll use  
obj[[internalId]] to denote internal property access and ^^ for  
intersection:

let lst :List = y[["this%"]] ^^ z[["%this"]];
if (lst.length == 0) throw TypeError("% not defined on operands");
if (lst.length != 1) throw TypeError("ambiguous % operation");
return lst[0](y, z);

What party is responsible here, given that the only way any pair of  
internal properties of the form y[["this%"]] and z[["%this"]] get  
defined (on constructor prototypes, per Allen's followup) is by  
Function.defineOperator being called with both y and z's  
"types" (constructors).

So (to repeat a point from my last post) we don't actually need (or  
want) internal instance properties to keep book -- we could use a  
separate lexically scoped two-dimensional mapping for the % operator  
indexed by "types" (see instanceof).

If the only observable difference between using internal instance  
properties and using a separate lexical operator matrix is that you  
claim Object.freeze(Point.prototype) should cause

   Function.defineOperator('+', Point, Number, pointPlusNumber);

or similar calls passing Point fail, then I side with Allen where he  
wrote:

"I wouldn't want to describe or specify these per object (or per  
prototype) operator lists as properties.  Too much baggage comes with  
that concept.  It'd just make them another internal characteristic of  
objects.  As already mentioned, if you think of them this way there  
shouldn't be any interactions with Object.freeze or any other existing  
semantics other than whatever we might choose to explicitly define."

But that's no surprise, because I'm in favor of letting Carol define  
multimethods for types purveyed by Alice and Bob, where Alice and Bob  
didn't foresee the combination Carol found useful.

I don't see how this is "irresponsible". If Alice and Bob freeze  
prototypes, Carol could still hack wrappers, in the absence of  
multimethods. If Alice and Bob don't want to provide their types to  
Carol, they can hide them otherwise. Hiding != Freezing.


> Were we to adopt multimethods, where atan2 somehow gets enhanced by  
> all imported modules defining overloadings of atan2 in that scope,  
> what is the value of the atan2 variable in that scope?

A multimethod.


> If it is a function composed of all lexical contributors, what  
> happens when this function is passed as a value and then used in a  
> different scope?

Multimethods [*] are first-class values, sets of associated type- 
specific statically scoped functions. No dynamic scope, of course.

You define a multimethod, let's say with new syntax or new  
Function.defineMultimethod or whatever strawman API you like (trade- 
offs about early error checking left for later discussion). So atan2  
is not just a "good old function". It's something new, an overloaded  
function that "has" multimethods (the ES4 proposal, FWIW, had new  
syntax like |generic function atan2(x, y);| to get things started).

(I don't mean to belabor this point, but I do want to stress that  
plain old functions can't be confused for multimethods and  
unintentionally mixed together. JS replaces any previous binding when  
processing a function declaration. This behavior would of course  
continue, but if we add multimethods, then we have the choice to make  
it an error to define a multimethod whose name is already bound to any  
value, or to a plain old function, or whatever seems best.)

Anyway, code with access to the distinguished multimethod's lexical  
binding then would add functions to it by denoting it in that scope or  
using a reference to it passed to another function which could add  
functions to the multimethod.

This is observably equivalent as far as I can tell to how Christian's  
Function.defineOperator would seem to work -- except of course that  
Function.defineOperator mutates internal "this+" and "+this"  
properties given a first argument of '+', whereas a  
Function.addMultimethod(atan2, Complex, Rational, function (c, r)  
{...}) API would take an explicit reference to the multimethod being  
augmented.

(EIBTI! :-P)

Finally, anyone with a reference (found in the lexical scope as the  
basis case, or passed to other functions to introduce them to the  
atan2 multimethod) can call atan2 on the several combinations of  
argument types defined in it. Dispatch works as in Dylan or Cecil, if  
we want to get fancy with subtypes; or simply as in Christian's  
proposal (zero inheritance FTW ;-).

Short on time, so I should stop here and ask what seems unclear or  
wrong.

/be

[*] The "method" name may mislead, but Java static methods don't have  
a distinguished receiver, so we can cope. I'm using the "multimethod"  
name instead of "generic method" or "generic function" -- generic  
methods or generic functions cause confusion with "generics", and with  
plain old JS functions that make few assumptions about argument types.




More information about the es-discuss mailing list