Operator overloading revisited

View: New views
20 Messages — Rating Filter:   Alert me  
< Prev | 1 - 2 - 3 | Next >

Operator overloading revisited

by Christian Plesner Hansen-3 :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

Following the decimal discussion where values came up I looked for
information on that.  The most substantial I could find was Mark's
operator overloading strawman.

I think the current strawman has a number of weaknesses.  It relies on
the (hypothetical) type system which makes it fundamentally different
from normal method dispatch.  It uses double dispatch for all
user-defined operators which makes optimization difficult.  It is
asymmetrical: the left hand side always controls dispatch.

I'd like to propose an alternative approach that avoids these
problems.  You could call it "symmetric" operator overloading.  When
executing the '+' operator in 'a + b' you do the following (ignoring
inheritance for now):

 1: Look up the property 'this+' in a, call the result 'oa'
 2: If oa is not a list give an error, no '+' operator
 3: Look up the property '+this' in b, call the result ob
 4: If ob is not a list give an error, no '+' operator
 5: Intersect the lists oa and ob, call the result r
 6: If r is empty give an error, no '+' operator
 7: If r has more than one element give an error, ambiguity
 8: If r[0], call it f, is not a function give an error
 9: Otherwise, evaluate f(a, b) and return the result.

The way to define a new operator is to add the same function to the
'this+' list of the left-hand side and the '+this' list on the
right-hand side.  This would probably be handled best by a simply
utility function, say Function.defineOperator:

  function pointPlusNumber(a, b) {
    return new Point(a.x + b, a.y + b);
  }

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

Using this approach it's easy to extend symmetrically:

  function numberPlusPoint(a, b) {
    return new Point(a + b.x, a + b.y);
  }

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

In this case you need two different functions, one for Point+Number
and one for Number+Point, but in many cases the same function can be
used for both:

  function addPoints(a, b) {
    return new Point(a.x + b.x, a.y + b.y);
  }

  Function.defineOperator('+', Point, Point, addPoints);

This approach is completely symmetric in the two operands.  Operator
dispatch is fairly similar to ordinary method dispatch and doesn't
rely on a type system.  Programmers don't have to manually implement
double dispatch.  It my be a bit more work to do lookup but on the
other hand it is straightforward to apply inline caching so often
you'll only need to do that work once.  It can be extended to deal
with inheritance -- you might consider all operators up through the
scope chain and pick the most specific one based on how far up the
scope chain you had to look.

It may look a bit foreign but I think it has a lot of nice properties.
 Comments?


-- Christian
_______________________________________________
es-discuss mailing list
es-discuss@...
https://mail.mozilla.org/listinfo/es-discuss

Re: Operator overloading revisited

by Mark S. Miller-2 :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

On Sun, Jun 28, 2009 at 7:05 AM, Christian Plesner Hansen <christian.plesner.hansen@...> wrote:
Following the decimal discussion where values came up I looked for
information on that.  The most substantial I could find was Mark's
operator overloading strawman.

I think the current strawman has a number of weaknesses.  It relies on
the (hypothetical) type system which makes it fundamentally different
from normal method dispatch.


Are you referring to <https://mail.mozilla.org/pipermail/es-discuss/2009-January/008535.html>? It starts with:

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
[...suggested restrictions on when this test applies, but all without presuming any "type"s beyond what ES5 already supports...]

I'm glad you started with one of the motivating problem cases: "+". The reason I conditioned the new overloadings on Operable is to handle the "+" case, which, to be upwards compatible, must continue to .toString() on legacy non-number objects. One of Sam's earlier decimal proposals ran aground on this "+" problem. IIUC, your proposal could deal with the legacy "+" problem by duck typing on "this+" and "+this"? This may be adequate.

My strawman was written without any concept of value types at the time. In committee, Waldemar then raised the "===" issue. "===" currently has the great virtues that it is a reliable equality test that does not run user code during the comparison, and whose veracity does not depend on user code. NaN and -0 aside, if x === y, then they are observably indistinguishable. Any occurrence of that value of x in a computation state can be substituted for any occurrence of that value of y in that computation state without effecting the semantics of that state. We did not want to lose those virtues, so we do not want to allow user defined abstractions to overload ===. Neither do we want

    Point(3, 5) === Point(3, 5)

to be false. AFAIK, the only way to reconcile these goals is to introduce value types. In earlier internal email I summarized the value-type thinking as:

* start with something like my operator-overloading-by-double-dispatch
  proposal,
* but tie the ability to respond to operators to being a value -- an
  instance of a value type/class.
* A value is necessarily frozen and compares "===" based on
  cycle-tolerant pairwise "===" comparison of named properties
  [missing from earlier email: as well as [[Prototype]] and [[Class]] properties].
* Therefore, a value is indistinguishable from a shallow copy of the
  same value. Strings are a precedent.
* Values would be able to overload "==" but not "===".
* "===" would remain reliable and not cause user code to run during
  the comparison.


It seems to me that the Operable-test and value-type issues are orthogonal to the core suggestion of your email: whether operator dispatch is based on Smalltalk-like double dispatch, in which the left side is asymmetrically in charge, or on mutual agreement which is indeed symmetric. The first bullet could be changed to your proposal without affecting the rest of the value-type issue.

I note that your symmetric suggestion avoids the problem of most other symmetric overloading systems, like Cecil, of diffusion of responsibility. Since your's only proceeds under mutual agreement, both operands are responsible for the result. Unfortunately, this means that new libraries like Point, in order to allow "pt + number" or "number + pt", must in practice monkey patch Number.prototype to know about these new overloads. Should Number.prototype in that frame (global object context) already be frozen to prevent such monkey patching, then this Point extension can no longer be loaded into that frame.

By comparison, the cool thing about double dispatch is that new types can arrange to work with old types without monkey patching, given only that the old types engage in the generic enabling default behavior and that the new types have explicit cases for the old types it knows about and wishes to work with. Might there be a way to combine the modular extension virtues of double-dispatch with the symmetric-responsibility virtues of your proposal? I don't know. But if so, that would be awesome!

--
   Cheers,
   --MarkM

_______________________________________________
es-discuss mailing list
es-discuss@...
https://mail.mozilla.org/listinfo/es-discuss

Re: Operator overloading revisited

by Christian Plesner Hansen-3 :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

> I note that your symmetric suggestion avoids the problem of most other
> symmetric overloading systems, like Cecil, of diffusion of responsibility.
> Since your's only proceeds under mutual agreement, both operands are
> responsible for the result. Unfortunately, this means that new libraries
> like Point, in order to allow "pt + number" or "number + pt", must in
> practice monkey patch Number.prototype to know about these new overloads.
> Should Number.prototype in that frame (global object context) already be
> frozen to prevent such monkey patching, then this Point extension can no
> longer be loaded into that frame.

Can you give me some context on freezing built-ins?  What's the
purpose?  If it is to prevent the behavior of numbers to change under
people's feet then you might argue that preventing libraries from
changing the behavior of '+' is a good thing.

Note that adding an operator doesn't actually have to change
Number.prototype -- what it changes is the list of operator functions.
 You could allow that even if Number.prototype is frozen.  It would
still be "monkey patching" but if you use something less general than
a list, say an OperatorCollection that you could only add to, maybe it
would be okay.  Again, I don't know the exact purpose of freezing
types this way so it's hard for me to tell if that solves the problem.
_______________________________________________
es-discuss mailing list
es-discuss@...
https://mail.mozilla.org/listinfo/es-discuss

Re: Operator overloading revisited

by Mark S. Miller-2 :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

On Sun, Jun 28, 2009 at 2:51 PM, Christian Plesner Hansen <christian.plesner.hansen@...> wrote:

Can you give me some context on freezing built-ins?  What's the
purpose?  If it is to prevent the behavior of numbers to change under
people's feet

yes
 
then you might argue that preventing libraries from
changing the behavior of '+' is a good thing.

For objects that already have a '+' behavior, yes. The issue isn't only built-in and operators. If library L1 defines constructor C1 that makes objects that respond in a certain way when their foo method is called, then it is monkey patching if library L2 loaded later modifies C1.prototype to alter the foo behavior of these instances. L1 may or may not be designed in the expectation that other libraries will monkey patch it. If it wishes to prevent such monkey patching, it can do so by freezing, say, C1 and C1.prototype.
 
However, if L2 introduces creates distinct objects that respond to foo in a different way, that is not a monkey patch of L1. Hence the introduction of a distinct Operable in the original proposal.

Note that adding an operator doesn't actually have to change
Number.prototype -- what it changes is the list of operator functions.
 You could allow that even if Number.prototype is frozen.  It would
still be "monkey patching" but if you use something less general than
a list, say an OperatorCollection that you could only add to, maybe it
would be okay.  Again, I don't know the exact purpose of freezing
types this way so it's hard for me to tell if that solves the problem.

Interesting. This is worth exploring.

--
   Cheers,
   --MarkM

_______________________________________________
es-discuss mailing list
es-discuss@...
https://mail.mozilla.org/listinfo/es-discuss

Re: Operator overloading revisited

by Brendan Eich-3 :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

On Jun 28, 2009, at 1:24 PM, Mark S. Miller wrote:

I note that your symmetric suggestion avoids the problem of most other symmetric overloading systems, like Cecil, of diffusion of responsibility.

"Diffusion" sounds like a problem, a bad thing, but consider (I've quoted this before) the use-case:

The generalization of receiver-based dispatch to multiple dispatch provides a number of advantages. For example, multimethods support safe covariant overriding in the face of subtype polymorphism, providing a natural solution to the binary method problem [Bruce et al. 1995; Castagna 1995]. More generally, multimethods are useful whenever multiple class hierarchies must cooperate to implement a method’s functionality. For example, the code for handling an event in an event-based system depends on both which event occurs and which component is handling the event.

MultiJava: Design Rationale, Compiler Implementation, and Applications
Curtis Clifton, Todd Millstein, Gary T. Leavens, and Craig Chambers


So there are use-cases involving responsible classes A and B (event source and sink), no third parties. But there are also the third party cases you're thinking of. The last time this came up (https://mail.mozilla.org/pipermail/es-discuss/2009-January/008715.html), I wrote:

Over and over, in Mozilla code and especially on the web, we've seen "code mashups" where one does not always have the money, or even the ability, to monkey-patch class A or class B to understand each other. Wrapping can be done to make a class C with operators, at some cost. Why this is always a feature and never a bug is not clear, and Chambers, et al. have researched a fix to it, viewing it as a bug to motivate their research.

I can't find a reply from you on this :-), so let me take this opportunity to ask: how does requiring a third party to create a wrapper class C discharge undiffused responsibility in any useful way?

A and B still are not involved in the wrapping done by class C through which A and B can become (wrapped) operands to an operator defined by C. Often the creation of such a wrapper amounts to expending effort to write tedious boilerplate that has non-trivial runtime and space overhead.

This may be a "religious" point over which we shouldn't argue -- Waldemar suggested that it was a difference of "points of view" at the TC39 meeting. But it seems to me taking one such point of view ought not knock down multimethods, at least not without more precision about what exactly is the problem with "diffusion of responsibility" that they bring.

/be

_______________________________________________
es-discuss mailing list
es-discuss@...
https://mail.mozilla.org/listinfo/es-discuss

Re: Operator overloading revisited

by Mark S. Miller-2 :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

On Mon, Jun 29, 2009 at 11:21 AM, Brendan Eich <brendan@...> wrote:
On Jun 28, 2009, at 1:24 PM, Mark S. Miller wrote:

I note that your symmetric suggestion avoids the problem of most other symmetric overloading systems, like Cecil, of diffusion of responsibility.

"Diffusion" sounds like a problem, a bad thing, but consider (I've quoted this before) the use-case:

The generalization of receiver-based dispatch to multiple dispatch provides a number of advantages. For example, multimethods support safe covariant overriding in the face of subtype polymorphism, providing a natural solution to the binary method problem [Bruce et al. 1995; Castagna 1995]. More generally, multimethods are useful whenever multiple class hierarchies must cooperate to implement a method’s functionality. For example, the code for handling an event in an event-based system depends on both which event occurs and which component is handling the event.


 Let's try a reductio ad absurdum. It seems to be that the argument you're making applies just as well to

    w.foo(x)

or

   bar(y,z)

In conventional oo reasoning, the first expression is making a request to the object bound to w. The second is making a request to function bound to bar. In both cases, the requested object is responsible for deciding what the next step is in responding to that request. The veracity of the result is according to the responsible object. If there's a bug in this result, the responsible object may not itself be the one that is buggy. However, the blame chain starts there.

What if w doesn't respond to a foo request? Currently our choices are

   1) rewrite our first expression so that it no longer asks w to foo.
   2) modify w or w.[[Prototype]] or something so that w knows how to foo.
       2a) Get the provider/author of w to do this and release a new version
       2b) Fork or rewrite the source for w
       2c) Monkey patch w or w.[[Prototype]] from new module M2
   3) Wrap the original w object with a foo-responding decorator
       3a) Conventional decoration, where other requests are manually forwarded
       3b) In JS, one can decorate by prototype inheritance
       3c) If we ever have catchalls, one might be able to use them for decoration

All the options above except for #2c maintain the locality of reponsibility that makes objects so pleasant to reason about. 2c has come to be regarded as bad practice. I suggest that one of the main reasons why is that it destroys this locality. w shouldn't be held responsible if it can't be responsible. One of the great things about Object.freeze is that w's provider can prevent 2c on w.

The Cecil-style operator overloading argument, extended to this example, would enable a fourth option

   4) Allow module M2 to say how w should respond to foo.

I grant that #4 is not as bad as #2c. But does anyone care to argue that #4 would be a good thing in general? If not, why would #4 be ok for operators but not method or function calls?

--
   Cheers,
   --MarkM

_______________________________________________
es-discuss mailing list
es-discuss@...
https://mail.mozilla.org/listinfo/es-discuss

Re: Operator overloading revisited

by Brendan Eich-3 :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

On Jun 29, 2009, at 11:55 AM, Mark S. Miller wrote:

On Mon, Jun 29, 2009 at 11:21 AM, Brendan Eich <brendan@...> wrote:
On Jun 28, 2009, at 1:24 PM, Mark S. Miller wrote:

I note that your symmetric suggestion avoids the problem of most other symmetric overloading systems, like Cecil, of diffusion of responsibility.

"Diffusion" sounds like a problem, a bad thing, but consider (I've quoted this before) the use-case:

The generalization of receiver-based dispatch to multiple dispatch provides a number of advantages. For example, multimethods support safe covariant overriding in the face of subtype polymorphism, providing a natural solution to the binary method problem [Bruce et al. 1995; Castagna 1995]. More generally, multimethods are useful whenever multiple class hierarchies must cooperate to implement a method’s functionality. For example, the code for handling an event in an event-based system depends on both which event occurs and which component is handling the event.


 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.

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.


It seems to be that the argument you're making applies just as well to

    w.foo(x)

or

   bar(y,z)

In conventional oo reasoning, the first expression is making a request to the object bound to w. The second is making a request to function bound to bar. In both cases, the requested object is responsible for deciding what the next step is in responding to that request. The veracity of the result is according to the responsible object. If there's a bug in this result, the responsible object may not itself be the one that is buggy. However, the blame chain starts there.

Sure, but reduction to an aburdity doesn't tell us which of the two diffuses responsibility or why that's bad, or if it's bad how the cost trumps other concerns.

Functions have their place, so does OOP. But OOP does not mean functions are obsolete -- consider Math.atan2, a function (not a method -- ignore the Math poor man's namespace!) taking two parameters x and y. Either x or y could be blame-worthy for some call (signed zero has a use-case with atan2, the numerics hackers tell me), but there's no single receiver responsible for computing the result given the other parameter as an argument. atan2 is a function, arguably similar to a division operator.

"Blame chain" is a good phrase, and of course we've been tracking PLT Scheme Contracts for a while and are still keeping them in mind for some harmonious future edition. But if responsibility is about blame, there are many ways to skin that cat. And there are other costs competing with the cost of weak or wrongful blame.

Since JS has functions, not only methods, we have benefits countervailing the cost of having to blame one of two or more actual parameters when analyzing for responsibility. bar(y, z) is tenable in JS interface design, depending on particulars, sometimes competitive with w.foo(x), occasionally clearly better.


What if w doesn't respond to a foo request? Currently our choices are

   1) rewrite our first expression so that it no longer asks w to foo.
   2) modify w or w.[[Prototype]] or something so that w knows how to foo.
       2a) Get the provider/author of w to do this and release a new version
       2b) Fork or rewrite the source for w
       2c) Monkey patch w or w.[[Prototype]] from new module M2
   3) Wrap the original w object with a foo-responding decorator
       3a) Conventional decoration, where other requests are manually forwarded
       3b) In JS, one can decorate by prototype inheritance
       3c) If we ever have catchalls, one might be able to use them for decoration

All the options above except for #2c maintain the locality of reponsibility that makes objects so pleasant to reason about.

Sorry, but you are assuming your conclusion -- the locality of responsibility and its pleasantness in all cases, over against other costs, is precisely the issue.

Experience on the web includes enough cases where third parties have to mate two abstractions in unforeseen ways. We seem to agree on that much. But your 1-3 do not prove themselves superior to 4 (multimethods, cited below) without some argument that addresses all the costs, not just blame-related costs.


2c has come to be regarded as bad practice. I suggest that one of the main reasons why is that it destroys this locality. w shouldn't be held responsible if it can't be responsible. One of the great things about Object.freeze is that w's provider can prevent 2c on w.

Assuming the conclusion ("One of the great things..."), I think -- although the argument seems incomplete ("one of", and no downside analysis).

Many JS users have already given voice (see ajaxian.com) to fear that freeze will be overused, making JS brittle and hard to extend, requiring tedious wrapping and delegating. This is a variation on Java's overused final keyword. Object.freeze, like any tool, can be misused. There are good use-cases for freeze, and I bet a few bad ones, but in any case with freeze in the language, monkey-patching is even harder to pull off over time.

That leaves forking or wrapping in practice. Why forking or wrapping should always win based on responsibility considerations, if one could use a Dylan or Cecil multimethod facility, is not at all clear. Forking has obvious maintenance and social costs. Wrapping has runtime and space costs as well as some lesser (but not always trivial) maintenance cost.

So why shouldn't we consider, just for the sake of argument, dispatch on the types of arguments to one of several functions bound to a given operator, in spite of the drawback (or I would argue, sometimes, because of the benefit) of the responsibility being spread among arguments -- just as it is for Math.atan2's x and y parameters?


The Cecil-style operator overloading argument, extended to this example, would enable a fourth option

   4) Allow module M2 to say how w should respond to foo.

I grant that #4 is not as bad as #2c. But does anyone care to argue that #4 would be a good thing in general?

Sure, and I've given references to some of the papers. There are others, about Dylan as well as Cecil. Here's a talk that considers "design patterns" (double dispatch with "reverse-foo" operators are pattern-y) to be workarounds for bugs in the programming language at hand:


Complete with multimethods!

If not, why would #4 be ok for operators but not method or function calls?

Straw man, happy to knock it down. I never said your item 4 wasn't ok for methods and function calls -- operators are the syntactic special form here, the generalization is either to methods (double-dispatched) or function (generic functions, operator multimethods).

The ES4 proposal (http://wiki.ecmascript.org/doku.php?id=proposals:generic_functions) for generic functions/methods indeed subsumed the operators proposal that preceded it. I'm not trying to revive it wholesale, please rest assured. But I am objecting to circular (or at best incomplete) arguments for dyadic operators via double dispatch of single-receiver methods.

As I've written before, if double dispatch is the best we can agree on for Harmony, then I'm in. But we should be able to agree on the drawbacks as well as the benefits, and not dismiss the former or oversell the latter.

/be


_______________________________________________
es-discuss mailing list
es-discuss@...
https://mail.mozilla.org/listinfo/es-discuss

Re: Operator overloading revisited

by P T Withington :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

On 2009-06-30, at 01:26EDT, Brendan Eich wrote:

> http://www.norvig.com/design-patterns/

+∞

The flip side of the "diffusion of responsibility" is the "solipsism  
of encapsulation", which falls down as soon as there is more than one  
self (or `this` :P).
_______________________________________________
es-discuss mailing list
es-discuss@...
https://mail.mozilla.org/listinfo/es-discuss

Re: Operator overloading revisited

by Brendan Eich-3 :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

On Jun 28, 2009, at 7:05 AM, Christian Plesner Hansen wrote:

I'd like to propose an alternative approach that avoids these
problems.  You could call it "symmetric" operator overloading.  When
executing the '+' operator in 'a + b' you do the following (ignoring
inheritance for now):

1: Look up the property 'this+' in a, call the result 'oa'
2: If oa is not a list give an error, no '+' operator
3: Look up the property '+this' in b, call the result ob
4: If ob is not a list give an error, no '+' operator
5: Intersect the lists oa and ob, call the result r
6: If r is empty give an error, no '+' operator
7: If r has more than one element give an error, ambiguity
8: If r[0], call it f, is not a function give an error
9: Otherwise, evaluate f(a, b) and return the result.

This reinvents asymmetric multimethod dispatch without any inheritance or subtype specificity, i.e. with single-element class precedence lists and therefore no need to judge best-fit or most-specific method among the lists. You just pick the one that matches, and it's an error if zero or more than one match. Lots of references from the nineties to relevant work, e.g.,


under "Design: Symmetric vs. Encapsulated Multimethods", which leads to the [Boyland 1997, Castagna 1995, Bruce 1995] refs. The ES4 generic functions proposal has refs at the bottom too. It's necessary to put on your subclass/subtype-filtering glasses when reading, of course, but then the images resolves into very much what you propose.

Also, if you know Python and haven't seen this yet, have a look at:


There are differences to debate between symmetric and asymmetric or encapsulated multimethods, and we can defer the CPL until (and it's not for sure) we have some user-extensible notion of subtyping, but I want to stress that the multimethod cat is out of the bag here. I like the gist of your proposal, it is a round-enough wheel. ;-)

Whether it should be extended to non-operator methods is something else we can debate, or defer. I claim there's nothing special about operators, and given other numeric types one might very well want atan2 multimethods.

/be

_______________________________________________
es-discuss mailing list
es-discuss@...
https://mail.mozilla.org/listinfo/es-discuss

RE: Operator overloading revisited

by Allen Wirfs-Brock-2 :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

Pulling back from Mark's and Brendan's programming language metaphysics discussion (which I think is interesting and useful) I just wanted to say that I find Christian's proposal quite interesting. It feels like a natural fit to the language as it exists today and doesn't require any new abstraction concepts such as classes, nominal type, value types, etc. although it could be made compatible with any of them. It has a simple conceptual explanation: an operator works with two object if they both associate the same function with that operator.

It may or may not be the ultimate solution, but I think it deserves serious consideration.

I do have a few thoughts and questions on the proposal as outlined:

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.

It's too bad that the "this+" and "+this" lists can't be merged but keeping them distinct seems essential to making this scheme work.  However, it also complicates the simple explanation.

I assume that your example really should be defining the operators on prototype objects. Eg:
   Function.defineOperator('+', Point.prototype, Number.prototype, pointPlusNumber);

In order to emphasize the essential difference between "this+" and "+this", I might order the arguments like:
   Function.defineOperator(Point.prototype,'+', Number.prototype, pointPlusNumber);

There seems like there are probably cases where you would only want to define a "this+" or "+this" binding and not both.

defineOperator probably should be a method on Object and there probably needs to be corresponding introspection methods.  This sort of usability engineering can be worked out if this proposal gains traction.

Allen


>-----Original Message-----
>From: es-discuss-bounces@... [mailto:es-discuss-
>bounces@...] On Behalf Of Christian Plesner Hansen
>Sent: Sunday, June 28, 2009 7:05 AM
>To: es-discuss@...
>Subject: Operator overloading revisited
>
>Following the decimal discussion where values came up I looked for
>information on that.  The most substantial I could find was Mark's
>operator overloading strawman.
>
>I think the current strawman has a number of weaknesses.  It relies on
>the (hypothetical) type system which makes it fundamentally different
>from normal method dispatch.  It uses double dispatch for all
>user-defined operators which makes optimization difficult.  It is
>asymmetrical: the left hand side always controls dispatch.
>
>I'd like to propose an alternative approach that avoids these
>problems.  You could call it "symmetric" operator overloading.  When
>executing the '+' operator in 'a + b' you do the following (ignoring
>inheritance for now):
>
> 1: Look up the property 'this+' in a, call the result 'oa'
> 2: If oa is not a list give an error, no '+' operator
> 3: Look up the property '+this' in b, call the result ob
> 4: If ob is not a list give an error, no '+' operator
> 5: Intersect the lists oa and ob, call the result r
> 6: If r is empty give an error, no '+' operator
> 7: If r has more than one element give an error, ambiguity
> 8: If r[0], call it f, is not a function give an error
> 9: Otherwise, evaluate f(a, b) and return the result.
>
>The way to define a new operator is to add the same function to the
>'this+' list of the left-hand side and the '+this' list on the
>right-hand side.  This would probably be handled best by a simply
>utility function, say Function.defineOperator:
>
>  function pointPlusNumber(a, b) {
>    return new Point(a.x + b, a.y + b);
>  }
>
>  Function.defineOperator('+', Point, Number, pointPlusNumber);
>
>Using this approach it's easy to extend symmetrically:
>
>  function numberPlusPoint(a, b) {
>    return new Point(a + b.x, a + b.y);
>  }
>
>  Function.defineOperator('+', Number, Point, numberPlusPoint);
>
>In this case you need two different functions, one for Point+Number
>and one for Number+Point, but in many cases the same function can be
>used for both:
>
>  function addPoints(a, b) {
>    return new Point(a.x + b.x, a.y + b.y);
>  }
>
>  Function.defineOperator('+', Point, Point, addPoints);
>
>This approach is completely symmetric in the two operands.  Operator
>dispatch is fairly similar to ordinary method dispatch and doesn't
>rely on a type system.  Programmers don't have to manually implement
>double dispatch.  It my be a bit more work to do lookup but on the
>other hand it is straightforward to apply inline caching so often
>you'll only need to do that work once.  It can be extended to deal
>with inheritance -- you might consider all operators up through the
>scope chain and pick the most specific one based on how far up the
>scope chain you had to look.
>
>It may look a bit foreign but I think it has a lot of nice properties.
> Comments?
>
>
>-- Christian
>_______________________________________________
>es-discuss mailing list
>es-discuss@...
>https://mail.mozilla.org/listinfo/es-discuss

_______________________________________________
es-discuss mailing list
es-discuss@...
https://mail.mozilla.org/listinfo/es-discuss

Re: Operator overloading revisited

by Brendan Eich-3 :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

On Jun 30, 2009, at 10:35 AM, Allen Wirfs-Brock wrote:

> Pulling back from Mark's and Brendan's programming language  
> metaphysics discussion (which I think is interesting and useful) I  
> just wanted to say that I find Christian's proposal quite  
> interesting. It feels like a natural fit to the language as it  
> exists today and doesn't require any new abstraction concepts such  
> as classes, nominal type, value types, etc. although it could be  
> made compatible with any of them. It has a simple conceptual  
> explanation: an operator works with two object if they both  
> associate the same function with that operator.
>
> It may or may not be the ultimate solution, but I think it deserves  
> serious consideration.

We can entertain strawman UI but I hope to focus on substance not  
surface.

Costs that seem to leap out from Christian's mutual-asymmetric  
multimethod proposal:

1. Two lookups, "this+" and "+this", required.
2. Intersection.

(2) looks like a transformation or simplification of standard  
multimethod Class Precedence List usage to find the most specific  
method. Any system allowing multiple methods for a given operator will  
have something like it.

(1) seems avoidable, though -- *assuming* it is not a phantom cost. We  
have to deal with the symmetric vs. encapsulated issue, though. But  
notice that Christian's proposal does not use "this" in the method  
bodies!

   function pointPlusNumber(a, b) {
     return new Point(a.x + b, a.y + b);
   }
   function numberPlusPoint(a, b) {
     return new Point(a + b.x, a + b.y);
   }
   function addPoints(a, b) {
     return new Point(a.x + b.x, a.y + b.y);
   }

These are nice generic functions. With type annotations or guards, you  
could imagine them as adding to generic function "+":

   generic function "+"(a :Point, b :Point) {
     return new Point(a.x + b.x, a.y + b.y);
   }

Syntax is not the point, please hold fire and spare the bikeshed --  
the unencapsulated (no |this| or |self|) nature of these functions is  
what I'm stressing.

So why require "this+" and "+this" lookups?

If the reason is to assign responsibility to the different  
constructors [*], then how can you tell who is responsible?

Function.defineOperator in the proposal binds both "this+" and  
"+this". I agree these should not be plain old properties. But then  
why spec them as named internal properties of any kind?

Suppose we had a generic function "+" (syntax TBD) to which methods  
could be added for particular non-conflicting pairs of operand types.  
Then the lookup would be for two types A and B, but this can be  
optimized under the hood. See, e.g.,

http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.35.4799

Seems worth allowing this kind of optimization. Whether and how users  
add multimethods we can debate at greater length. But can we agree  
that the "this+" and "+this" properties are not necessary?

/be

[*] or constructor.prototypes in your reply, but I don't see why we  
would require noisy ".prototype" suffixing -- instanceof does not!
_______________________________________________
es-discuss mailing list
es-discuss@...
https://mail.mozilla.org/listinfo/es-discuss

Re: Operator overloading revisited

by P T Withington :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

On 2009-06-30, at 14:06EDT, Brendan Eich wrote:

> These are nice generic functions. With type annotations or guards,  
> you could imagine them as adding to generic function "+":
>
>  generic function "+"(a :Point, b :Point) {
>    return new Point(a.x + b.x, a.y + b.y);
>  }
>
> Syntax is not the point, please hold fire and spare the bikeshed --  
> the unencapsulated (no |this| or |self|) nature of these functions  
> is what I'm stressing.

+∞ again

> If the reason is to assign responsibility to the different  
> constructors [*]

I don't understand the "assign responsibility" argument.  Is this  
about aligning (conflating?) security/access-control with classes/
instances?

Isn't a generic function a class that you could assign responsibility  
to?

_______________________________________________
es-discuss mailing list
es-discuss@...
https://mail.mozilla.org/listinfo/es-discuss

RE: Operator overloading revisited

by Allen Wirfs-Brock-2 :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

>>From: Mark S. Miller
.
>What if w doesn't respond to a foo request? Currently our choices are
>>
...
>>       2c) Monkey patch w or w.[[Prototype]] from new module M2
...

>>All the options above except for #2c maintain the locality of reponsibility that makes objects so pleasant to reason about. 2c has come to be regarded as bad practice. I suggest that one of the main reasons why is that it destroys this locality. w shouldn't be held responsible if it can't be responsible. One of the great things about Object.freeze is that w's provider can prevent 2c on w.

>From: Brendan Eich
...
>Many JS users have already given voice (see ajaxian.com) to fear that freeze will be overused, making JS brittle and hard to extend, requiring tedious wrapping and delegating. This is a variation on Java's overused final keyword. Object.freeze, like any tool, can be misused. There are good use-cases for freeze, and I bet a few bad ones, but in any case with freeze in the language, monkey-patching is even harder to pull off over time.

I believe that the original experience base with this style of monkey patching was in Smalltalk where it provide to be both highly useful and also gained a reputation as a bad practice.  We need to look at both sides of that equation.

Smalltalk programming was largely about reuse and monkey patching was highly useful because when combining (reusing) independently created implementation "modules" into a larger application you often need to pragmatically do some tweaking around the edges of the modules in order to make them fit together.  Even if you have access to the source of each module (which you typically did in Smalltalk) it was still better (more maintainable) to package the necessary integration code as part of the consuming application module rather than creating modified versions of the pre-existing modules that incorporates the integration code.

The "bad practice" rap came about because in the original Smalltalk environments multiple conflicting occurrences of such monkey patching applied on the same classes would silently stomp on each other and you would get some inconsistent combination of behaviors that were difficult to test for and hence caused downstream errors. Patches that conflict with each other are fundamentally incompatible and require human intervention to resolve. Digitalk's Team/V  solved this problem by treating such monkey patching as being declarative in nature and detecting any conflicting monkey patches at integration time (load time, build time, whatever..).  Such a conflict were treated as a "hard error" (comparable to a syntax error). With this mechanism developers could monkey patch to their hearts content, "mashing up" all sorts of independently created code but as soon as the patches stated stepping on each other they found out and could fix the problem.

I've often remarked that to me, the web feels like one big persistent Smalltalk virtual image. It's being continually extended and modified but it can never be recreated from scratch or redesigned as a whole. In such an environment you have to evolve in place and sometimes that means dynamically patching what is already there in order to make it work with something new or to use it for some new purpose. Web technologies need to be dynamic, flexible, and forgiving in order to accommodate this sort of evolution. We can provide mechanisms to help developers detect and deal with common failure scenarios but if the web doesn't stay flexible enough to allow for the possibility of such failures than it won't be able to evolve and will eventually be replaced by something that can

Allen

_______________________________________________
es-discuss mailing list
es-discuss@...
https://mail.mozilla.org/listinfo/es-discuss

Re: Operator overloading revisited

by Mark S. Miller-2 :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

On Mon, Jun 29, 2009 at 10:26 PM, Brendan Eich <brendan@...> 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.
 

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:

It seems to be that the argument you're making applies just as well to

    w.foo(x)

or

   bar(y,z)
I include the bar(y,z) as an example of case against multimethods. Since you include Math.atan2() in the case for multimethods, we can focus on it, and ignore either operators or method calls.
 

In conventional oo reasoning, the first expression is making a request to the object bound to w. The second is making a request to function bound to bar. In both cases, the requested object is responsible for deciding what the next step is in responding to that request. The veracity of the result is according to the responsible object. If there's a bug in this result, the responsible object may not itself be the one that is buggy. However, the blame chain starts there.

Sure, but reduction to an aburdity doesn't tell us which of the two diffuses responsibility or why that's bad, or if it's bad how the cost trumps other concerns.

Functions have their place, so does OOP. But OOP does not mean functions are obsolete -- consider Math.atan2, a function (not a method -- ignore the Math poor man's namespace!) taking two parameters x and y.

Good. Hereafter, this example is simply atan2(x,y).
 
Either x or y could be blame-worthy for some call (signed zero has a use-case with atan2, the numerics hackers tell me), but there's no single receiver responsible for computing the result given the other parameter as an argument. atan2 is a function, arguably similar to a division operator.

No. This is the crux. atan2 is a variable bound to a value in some lexical scope. In different scopes it may be bound to different values. In the expression atan2(x,y), the responsible party is the atan2 function. It decides the next step in processing this request, and decides in what way it will subcontract parts of the job to its x and y arguments.
 

"Blame chain" is a good phrase, and of course we've been tracking PLT Scheme Contracts for a while and are still keeping them in mind for some harmonious future edition. But if responsibility is about blame, there are many ways to skin that cat. And there are other costs competing with the cost of weak or wrongful blame.

Since JS has functions, not only methods, we have benefits countervailing the cost of having to blame one of two or more actual parameters when analyzing for responsibility. bar(y, z) is tenable in JS interface design, depending on particulars, sometimes competitive with w.foo(x), occasionally clearly better.

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.
 


What if w doesn't respond to a foo request? Currently our choices are

   1) rewrite our first expression so that it no longer asks w to foo.
   2) modify w or w.[[Prototype]] or something so that w knows how to foo.
       2a) Get the provider/author of w to do this and release a new version
       2b) Fork or rewrite the source for w
       2c) Monkey patch w or w.[[Prototype]] from new module M2
   3) Wrap the original w object with a foo-responding decorator
       3a) Conventional decoration, where other requests are manually forwarded
       3b) In JS, one can decorate by prototype inheritance
       3c) If we ever have catchalls, one might be able to use them for decoration

All the options above except for #2c maintain the locality of reponsibility that makes objects so pleasant to reason about.

Sorry, but you are assuming your conclusion -- the locality of responsibility and its pleasantness in all cases, over against other costs, is precisely the issue.

Experience on the web includes enough cases where third parties have to mate two abstractions in unforeseen ways. We seem to agree on that much. But your 1-3 do not prove themselves superior to 4 (multimethods, cited below) without some argument that addresses all the costs, not just blame-related costs.

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? 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?

What about a global atan2 (as would seem to be suggested by the Math.atan2 example you started with)? Would modules dynamically loaded into the same global environment be able to dynamically enhance the value already bound to the atan2 variable, changing the behavior of the atan2 value that may have already been passed to other scopes? Presumably this would fail if atan2 is frozen, as it should, since such mutation monkey patches the atan2 value.

Or would it bind a new value to the atan2 variable, where that new value has the new behavior? Presumably this would fail if atan2 is const, as it should.


2c has come to be regarded as bad practice. I suggest that one of the main reasons why is that it destroys this locality. w shouldn't be held responsible if it can't be responsible. One of the great things about Object.freeze is that w's provider can prevent 2c on w.

Assuming the conclusion ("One of the great things..."), I think -- although the argument seems incomplete ("one of", and no downside analysis).

Please reread the quote above. I agree that the argument I present here for Object.freeze is incomplete. Since the purpose of this thread is not to decide whether Object.freeze is or is not a good idea, I did not feel it necessary to enumerate all the arguments on either side of this question. But neither am I assuming my conclusion. I am simply enumerating one of these arguments -- that Object.freeze is good because it enables w's provider be responsible for the behavior for which we'd like to hold it responsible. You may disagree with this argument, but I don't see how it assumes its conclusion.
 

Many JS users have already given voice (see ajaxian.com) to fear that freeze will be overused, making JS brittle and hard to extend, requiring tedious wrapping and delegating.

I couldn't quickly find these arguments. Could you provide some links? Thanks.
 
This is a variation on Java's overused final keyword.

Vastly underused IMO, due to syntactic overhead. In the early E language, it was easier to declare a variable let-like than const-like. One of the best changes we made was to reverse this. E variables are now const-like unless stated otherwise. Everyone, whether concerned with security or not, has found this to be a pleasant change to the language. When reading code and wondering about a variable, once you see that it's declared const-like, you can avoid many follow-on questions.
 
Object.freeze, like any tool, can be misused. There are good use-cases for freeze, and I bet a few bad ones,

Of course. Good things are good and bad things are bad ;).
 
but in any case with freeze in the language, monkey-patching is even harder to pull off over time.

As goto and pointer arithmetic became harder in earlier language evolution. I sure hope so. Especially for involuntary monkey patching, which could be seen as an attack from the victim's perspective.

That leaves forking or wrapping in practice. Why forking or wrapping should always win based on responsibility considerations, if one could use a Dylan or Cecil multimethod facility, is not at all clear. Forking has obvious maintenance and social costs. Wrapping has runtime and space costs as well as some lesser (but not always trivial) maintenance cost.

Forking is indeed quite costly and should be avoided when there's a less bad alternative. Wrapping (or decorating) may or may not be a good pattern to use depending on the particulars.


So why shouldn't we consider, just for the sake of argument, dispatch on the types of arguments to one of several functions bound to a given operator, in spite of the drawback (or I would argue, sometimes, because of the benefit) of the responsibility being spread among arguments -- just as it is for Math.atan2's x and y parameters?

We are considering it. That's what this discussion is about. But we need to separate two kinds of considerations:

1) In a language in which all these abstraction mechanisms are present and used, which one should we use for a given problem? This will often be the kind of tradeoff you are explaining, where sometimes monkey patching or multimethods win. Indeed, the Cajita runtime library as implemented on ES3 internally monkey patches some of the ES3 primordials. Were I programming in Common Lisp and trying to make use of preexisting ibraries, I'm sure I'd use multimethods and packages. OTOH, Common Lisp's bloat and incoherence is one of the reasons I've always avoided it.

2) In a language with an adequate set of good abstraction mechanisms and already burdened by a plethora of bad or broken abstraction mechanisms, when is it worth adding a new abstraction mechanism?

The bar on #2 must be much higher than #1. If the language in the absence of the proposed abstraction mechanism already has some pleasant formal properties that the addition of the new mechanism would destroy, then the loss of this property must be added as a cost in assessing #2. But not #1, since that battle is already lost.

The Cecil-style operator overloading argument, extended to this example, would enable a fourth option

   4) Allow module M2 to say how w should respond to foo.

I grant that #4 is not as bad as #2c. But does anyone care to argue that #4 would be a good thing in general?

Sure, and I've given references to some of the papers. There are others, about Dylan as well as Cecil. Here's a talk that considers "design patterns" (double dispatch with "reverse-foo" operators are pattern-y) to be workarounds for bugs in the programming language at hand:


Complete with multimethods!

Well, Peter's my boss, so I concede the case. Let's add multimethods. Just kidding!
 
Actually, thanks for the link. I hadn't seen it before, and it is a nice presentation.

If not, why would #4 be ok for operators but not method or function calls?

Straw man, happy to knock it down.

Huh?
 
I never said your item 4 wasn't ok for methods and function calls -- operators are the syntactic special form here, the generalization is either to methods (double-dispatched) or function (generic functions, operator multimethods).

The ES4 proposal (http://wiki.ecmascript.org/doku.php?id=proposals:generic_functions) for generic functions/methods indeed subsumed the operators proposal that preceded it. I'm not trying to revive it wholesale, please rest assured. But I am objecting to circular (or at best incomplete) arguments for dyadic operators via double dispatch of single-receiver methods.

As I've written before, if double dispatch is the best we can agree on for Harmony, then I'm in. But we should be able to agree on the drawbacks as well as the benefits, and not dismiss the former or oversell the latter.

I certainly agree that it's good to have a more complete enumeration of the pros and cons. I think we largely agree on many of these but disagree on the weights. JavaScript today has adequate support for modularity and abstraction, and is mostly understandable. ES5/strict even more so. Common Lisp and PL/1 never were understandable. And Common Lisp is highly immodular. I agree that ES6 should grow some compared to ES5, but I highly value retaining ES5/strict's modularity, simplicity, understandability.

--
   Cheers,
   --MarkM

_______________________________________________
es-discuss mailing list
es-discuss@...
https://mail.mozilla.org/listinfo/es-discuss

Re: Operator overloading revisited

by Christian Plesner Hansen-3 :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

Sorry about falling behind in a discussion I started.  I'll try to catch up.

> Are you referring to
> <https://mail.mozilla.org/pipermail/es-discuss/2009-January/008535.html>? It
> starts with:
>
>>> 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
>
> [...suggested restrictions on when this test applies, but all without
> presuming any "type"s beyond what ES5 already supports...]

Yes that's the one.  The main problem I had was with the use of
"instanceof".  However, as I read the proposal again I've become
unsure if I've read something into it that it doesn't say.  The way I
understood it you would implement the + operator for Point instances
something like:

Point.prototype['+'] = function (that) {
  if (that instanceof Point) {
    return new Point(this.x + that.x, this.y + that.y);
  } else {
    return that['reverse+'](this);
  }
};

// Ditto 'reverse+'

Is that correct?

> ===. Neither do we want
>
>     Point(3, 5) === Point(3, 5)
>
> to be false.

Don't we?  I can see that we want Point(3, 5) == Point(3, 5) to be true but ===?

> It seems to me that the Operable-test and value-type issues are orthogonal
> to the core suggestion of your email: whether operator dispatch is based on
> Smalltalk-like double dispatch, in which the left side is asymmetrically in
> charge, or on mutual agreement which is indeed symmetric. The first bullet
> could be changed to your proposal without affecting the rest of the
> value-type issue.

That's probably true, though I don't know how Operable solves the '+'
problem so I couldn't say for sure.
_______________________________________________
es-discuss mailing list
es-discuss@...
https://mail.mozilla.org/listinfo/es-discuss

Re: Operator overloading revisited

by Christian Plesner Hansen-3 :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

> Whether it should be extended to non-operator methods is something else we
> can debate, or defer. I claim there's nothing special about operators, and
> given other numeric types one might very well want atan2 multimethods.

I did briefly consider listing "generalizes to full multimethods" as
one of the advantages but decided not to because I wasn't sure if
that's a good or a bad thing...
_______________________________________________
es-discuss mailing list
es-discuss@...
https://mail.mozilla.org/listinfo/es-discuss

Re: Operator overloading revisited

by Mark S. Miller-2 :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message


I certainly agree that it's good to have a more complete enumeration of the pros and cons. I think we largely agree on many of these but disagree on the weights. JavaScript today has adequate support for modularity and abstraction, and is mostly understandable. ES5/strict even more so. Common Lisp and PL/1 never were understandable. And Common Lisp is highly immodular. I agree that ES6 should grow some compared to ES5, but I highly value retaining ES5/strict's modularity, simplicity, understandability.

I withdraw the seeming comparison to Common Lisp. I was trying to make a more general point, but the implied comparison is not fair. Let's take Cecil instead, since it was designed by people with extraordinarily good taste (cc'ed), and since you recommended it as an example of multimethods done right. On your recommendation, I read it. Because of its pedigree, I really wanted to like it. In the end, I was reluctantly repelled by its complexity. If it is representative of the complexity costs of multimethods done right, I consider these costs to far outweight the benefits. Other things being equal, I would chose not to program in Cecil.

--
   Cheers,
   --MarkM

_______________________________________________
es-discuss mailing list
es-discuss@...
https://mail.mozilla.org/listinfo/es-discuss

Re: Operator overloading revisited

by Mark S. Miller-2 :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

On Tue, Jun 30, 2009 at 1:37 PM, Christian Plesner Hansen <christian.plesner.hansen@...> wrote:
Sorry about falling behind in a discussion I started.  I'll try to catch up.

> Are you referring to
> <https://mail.mozilla.org/pipermail/es-discuss/2009-January/008535.html>? It
> starts with:
>
>>> 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
>
> [...suggested restrictions on when this test applies, but all without
> presuming any "type"s beyond what ES5 already supports...]

Yes that's the one.  The main problem I had was with the use of
"instanceof".  However, as I read the proposal again I've become
unsure if I've read something into it that it doesn't say.  The way I
understood it you would implement the + operator for Point instances
something like:

Point.prototype['+'] = function (that) {
 if (that instanceof Point) {
   return new Point(this.x + that.x, this.y + that.y);
 } else {
   return that['reverse+'](this);
 }
};

// Ditto 'reverse+'

Is that correct?

Almost. It's actually a bit worse than that, since presumaby, Points want to be addable with Numbers:

Point.prototype['+'] = function (that) {
  if (that instanceof Point) {
   return new Point(this.x + that.x, this.y + that.y);
 } else if (that instanceof Number) {
   // again ignoring the difference between numbers and Numbers
   return new Point(this.x + that, this.y + that);
 } else {
   return that['reverse+'](this);
 }
};
 

> ===. Neither do we want
>
>     Point(3, 5) === Point(3, 5)
>
> to be false.

Don't we?  I can see that we want Point(3, 5) == Point(3, 5) to be true but ===?

Perhaps Complex(3, 5) === Complex(3, 5) is a better example?

In any case, I could live with these being false in order to avoid adding value types to the language. On this one, I'm on the fence.
 

> It seems to me that the Operable-test and value-type issues are orthogonal
> to the core suggestion of your email: whether operator dispatch is based on
> Smalltalk-like double dispatch, in which the left side is asymmetrically in
> charge, or on mutual agreement which is indeed symmetric. The first bullet
> could be changed to your proposal without affecting the rest of the
> value-type issue.

That's probably true, though I don't know how Operable solves the '+'
problem so I couldn't say for sure.

Ignoring Operable, in your proposal, how would you handle the need to preserve the legacy + behavior on legacy kinds of objects? The only other possibility I foresee is duck typing on the presence of the 'this+' and/or '+this' property names. That would work.

--
   Cheers,
   --MarkM

_______________________________________________
es-discuss mailing list
es-discuss@...
https://mail.mozilla.org/listinfo/es-discuss

Re: Operator overloading revisited

by Mark S. Miller-2 :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

On Tue, Jun 30, 2009 at 1:51 PM, Christian Plesner Hansen <christian.plesner.hansen@...> wrote:
> Whether it should be extended to non-operator methods is something else we
> can debate, or defer. I claim there's nothing special about operators, and
> given other numeric types one might very well want atan2 multimethods.

I did briefly consider listing "generalizes to full multimethods" as
one of the advantages but decided not to because I wasn't sure if
that's a good or a bad thing...

Do have a look at Cecil. Multimethods probably can't get much better than that.


--
   Cheers,
   --MarkM

_______________________________________________
es-discuss mailing list
es-discuss@...
https://mail.mozilla.org/listinfo/es-discuss

Re: Operator overloading revisited

by Brendan Eich-3 :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

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

> On Mon, Jun 29, 2009 at 10:26 PM, Brendan Eich <brendan@...>  
> 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.


_______________________________________________
es-discuss mailing list
es-discuss@...
https://mail.mozilla.org/listinfo/es-discuss
< Prev | 1 - 2 - 3 | Next >