Operator overloading revisited

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

Re: Operator overloading revisited

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

Reply to Author | View Threaded | Show Only this Message

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

The really nice thing about this from a cost standpoint is that it
caches well.  If changes to the operators understood by an object are
reflected in the object's hidden class (to use v8 terminology) the
same way changes to methods are, all the same caching techniques can
be used.  A global (hidden class, hidden class, operator)->function
cache can be used to ensure that the full operator resolution only has
to be done once for any operand type pair, after which the result can
be fetched by a simple hash table lookup.  Similarly, inline caching
can ensure that the hash lookup only has to be done once for any
monomorphic call site; megamorphic call sites, which I would expect
were rare, would have to do a hash lookup but not full operator
resolution.

By the way, did you say "mutual-asymmetric multimethod" :-)?

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

Methods live on objects and their prototypes.  Operators should too.
Using a special internal property rather than plain old properties is
fine by me but they should be directly associated with the objects
involved.  I'd argue why that is so important but I'd like to be sure
I understand what you're proposing as an alternative.  As I understand
it you would associate the operators with the constructor functions
similar to python's five minute multimethods.  Is that correct?
_______________________________________________
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

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

I'm not anti-multimethods.  If I was doing a language from scratch it
would have multimethods.  That doesn't mean that adding them to JS is
a good idea, or that mentioning them would further my case.

A side note: while cecil's model is nice and clean I wouldn't say that
it was the platonic ideal of a multimethod model.  For instance, I
think it's reliance on implementation/representation types for
dispatch rather than interface could be improved upon.  But that's a
whole other discussion.
_______________________________________________
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

> 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);
>  }
> };

I find the use of instanceof problematic for several reasons.

The connection between constructor functions and instances is fragile.
 Once you've created an instance you're free to change the constructor
without those changes being reflected in the instance.  What it means
to be a Point can change throughout the lifetime of a program.

Furthermore, instanceof is an implementation type query, not an
interface or behavior type query.  The limitations of implementation
types compared with interface types are well understood.

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

I really don't see it.  What is the problem with two complex numbers
being distinct object instances and strict equals reflecting that
fact?  If you want structural equality use ==, that's what it's there
for.

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

Duck typing is built into the proposal already.  It is folded into
step 2 and 4 as a special case of bailing out if looking up the
properties doesn't yield lists.  If an internal property were used
instead it would have to have a state that reflected "no operators are
present", maybe the absence of the internal property, which would
serve the same purpose.  Instead of giving an error in step 2 and 4
you would fall through to the legacy behavior if either side didn't
respond to operators.
_______________________________________________
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 Jul 1, 2009, at 12:48 AM, Christian Plesner Hansen wrote:

(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!

The really nice thing about this from a cost standpoint is that it
caches well.  If changes to the operators understood by an object are
reflected in the object's hidden class (to use v8 terminology) the
same way changes to methods are, all the same caching techniques can
be used.

Implementations can optimize whether Function.defineOperator binds internal "this+" and "+this" or adds a multimethod.


A global (hidden class, hidden class, operator)->function
cache can be used to ensure that the full operator resolution only has
to be done once for any operand type pair, after which the result can
be fetched by a simple hash table lookup.

This cuts both ways. A multimethod reifies that "global" (lexically scoped, rather) cache.


 Similarly, inline caching
can ensure that the hash lookup only has to be done once for any
monomorphic call site; megamorphic call sites, which I would expect
were rare, would have to do a hash lookup but not full operator
resolution.



By the way, did you say "mutual-asymmetric multimethod" :-)?

Sorry, I kept typing "asymmetric" when I meant "symmetric". Tired here (new baby at home :-)).

Adding "mutual" didn't help, did it? I was trying to capture the reciprocal "this+"/"+this" intersection requirement. But it still seems to me any such internal properties should constitute an unobservable specification/implementation device.

Below you allow for internal properties, so presumably ("this+" in a) would *not* evaluate to true for (a + b) in your examples. So why require"this+" and "+this" bindings at all? Caching under the hood is still doable but it is an implementation detail.


Methods live on objects and their prototypes.  Operators should too.

That's dogma, and it doesn't prove itself to unbelievers.

Why should operators be methods? Binary operators have no distinguished receiver. Indeed your proposal has nice generic functions for addition, no |this| at all. So much for "methods".

Saying that methods should "live" (be bound) somewhere when they do not use |this| to denote the receiver in which they are bound (directly or on the prototype chain) seems like even weaker dogma. Mark's double-dispatch is consistent with OOP. Your proposal seems to require reciprocal external property mutations on prototypes but only so the dispatch algorithm can do its simplified CPL thing. Why?


Using a special internal property rather than plain old properties is
fine by me but they should be directly associated with the objects
involved.

Directly? You wrote "and their prototypes" above, and your Function.defineOperator examples were passed constructors (from which to mutate prototypes), not direct instances:

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


  I'd argue why that is so important but I'd like to be sure
I understand what you're proposing as an alternative.  As I understand
it you would associate the operators with the constructor functions
similar to python's five minute multimethods.  Is that correct?

Yes.

/be

_______________________________________________
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 Jul 1, 2009, at 1:33 AM, Christian Plesner Hansen wrote:

>> 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);
>>  }
>> };

BTW, Mark's code reminds me of a paper on tracing double-dispatch I've  
mentioned in past talks:

http://www.ics.uci.edu/~franz/Site/pubs-pdf/ICS-TR-07-10.pdf

The approach of double-dispatching on what you know and bottoming out  
in a reverse-foo call when you don't know the other operand's type to  
me looks like an application "when in doubt, use brute  
force" (Thompson).

That can be an effective strategy, provided you're truly in doubt  
about better ways to solve the problem. But it is verbose and probably  
error prone. I much prefer something like your

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

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

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

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

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

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

to the if/else mess cited above (plus the reverse+ other half).

How much sweeter could this be sugared?

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

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

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

(Here the quoted operator names imply multimethod addition, where  
previously I used |generic function| to Tucker's enthusiastic +∞).

A programming language should not necessarily require low-level coding  
from its users just to conserve dispatch mechanism. Users are the  
masters, we specifiers and implementors are the servants.

Yes, adding a novel dispatch mechanism adds complexity and cognitive  
load for those learning the whole language. No, not everyone learns  
the whole language at once. Complexity can be contained if the new  
dispatch machinery is well-separated and provided it composes well.  
Done right, it's not going to burn any n00bs.

Sure, we could leave out multimethods and use double dispatch. We  
could leave out operators altogether and make 'em eat functions! But  
the motivation here is usability, not theoretical computability.


> I find the use of instanceof problematic for several reasons.

Yeah, that's why I used "types" in scare-quotes. But for now can we  
defer the interface or behavior ("type" as a four-letter word) debate,  
and try to agree on multimethods vs. double dispatch?


>> Perhaps Complex(3, 5) === Complex(3, 5) is a better example?
>
> I really don't see it.  What is the problem with two complex numbers
> being distinct object instances and strict equals reflecting that
> fact?  If you want structural equality use ==, that's what it's there
> for.

No, because for two numbers a and b, a == b <=> a === b. In fact for  
any two values, (typeof a == typeof b && a == b) <=> a === b. If we  
want to support usable numeric and similar "scalar" type extensions,  
we might want to preserve this logic.

/be
_______________________________________________
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

> This cuts both ways. A multimethod reifies that "global" (lexically scoped,
> rather) cache.

Sure, I'm not saying it's unique to my proposal.  I mention it because
it's an important aspect to keep in mind when considering the cost of
implementing it in practice.

> Sorry, I kept typing "asymmetric" when I meant "symmetric". Tired
> here (new baby at home :-)).

Congratulations!  And congratulations on firefox 3.5 too by the way!

> Below you allow for internal properties, so presumably ("this+" in a) would
> *not* evaluate to true for (a + b) in your examples. So why require"this+"

I'm all for internal properties rather than "this+" and "+this".

>> Methods live on objects and their prototypes.  Operators should too.
>
> Saying that methods should "live" (be bound) somewhere when they do not use
> |this| to denote the receiver in which they are bound (directly or on the
> prototype chain) seems like even weaker dogma. Mark's double-dispatch is
> consistent with OOP. Your proposal seems to require reciprocal external
> property mutations on prototypes but only so the dispatch algorithm can do
> its simplified CPL thing. Why?

All things being equal I'd probably rather have operator functions
outside objects or prototypes.  But things are not equal.

Relying on a connection between constructor functions and instances is
fragile, as I mentioned in a previous message to Mark about
instanceof.  You can make changes to a constructor function that is
not reflected in existing instances.  You can make changes both to
instances and constructors that disconnect the two completely.
Constructor functions are implementation types (as opposed to
interface or behavior types) which is inherently problematic.  I
wouldn't make this weak connection the cornerstone of operator
dispatch.

The connection between prototypes and instances is a much more direct
one.  If you change a prototype that change is reflected in the
instances (modulo shadowing).  If I want my type B to have all the
same behavior as A I can mix all the methods of A into B's prototype
and I will in effect have made B a subtype of A.  If operators reside
in A's prototype, in whatever form, I can do the same for operators.
Whether or not A's '+' operator uses 'this', it's still a part of A's
behavior and the most consistent place to put it is where A's other
behavior is, its prototype.

>> Using a special internal property rather than plain old properties is
>> fine by me but they should be directly associated with the objects
>> involved.
>
> Directly? You wrote "and their prototypes" above, and your
> Function.defineOperator examples were passed constructors (from which to
> mutate prototypes), not direct instances:

Right, I could have been more explicit about that.  I do mean
including prototypes, they fall under "objects involved".
_______________________________________________
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-07-01, at 03:48EDT, Christian Plesner Hansen wrote:

> Methods live on objects and their prototypes.

Only if you co-opt the word "method" to mean that.  I would claim this  
is just shorthand for "instance method".  There is also "class method"  
or "static method".  There are other definitions (see below).

On 2009-07-01, at 10:23EDT, Brendan Eich wrote:

> How much sweeter could this be sugared?
>
> function "+"(a :Point, b :Number) {
>   return new Point(a.x + b, a.y + b);
> }
>
> function "+"(a :Number, b :Point) {
>   return new Point(a + b.x, a + b.y);
> }
>
> function "+"(a :Point, b :Point) {
>   return new Point(a.x + b.x, a.y + b.y);
> }
>
> (Here the quoted operator names imply multimethod addition, where  
> previously I used |generic function| to Tucker's enthusiastic +∞).

Indeed.

Not to suggest paint, but I think the syntactic sugar of Dylan has a  
nice color:

`a + b`  is sugar for `\+(a, b)`

`\+` is a generic function (of 2 arguments) to which you can add  
"methods".  Methods are just syntactic sugar for specifying the  
branches of a dispatch algorithm (http://portal.acm.org/citation.cfm?id=236338.236343 
).  [That is since `+` is not a legal identifier, you have to escape  
it to use it as an identifier, to name a generic function.]

If "method" has to be used uniquely in Javascript as the word that  
means a computation attached to an instance, maybe we just need a new  
word to mean "dispatch element of a generic function".  I would have  
said "generic function method", which (ambiguously) gets abbreviated  
to "method", and may be the source of objection?


_______________________________________________
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 Jul 1, 2009, at 8:14 AM, Christian Plesner Hansen wrote:

>> This cuts both ways. A multimethod reifies that "global" (lexically  
>> scoped,
>> rather) cache.
>
> Sure, I'm not saying it's unique to my proposal.  I mention it because
> it's an important aspect to keep in mind when considering the cost of
> implementing it in practice.

All of us optimizing JS VM hackers agree, you will have no failure to  
object to over-specification that deoptimizes or rules out important  
optimizations. But the spec should not overspecify in favor of a  
particular optimization either, since what is observable other than  
performance won't differ between the overspecified standard and a  
hypothetical "just right" spec.

Of course getting it "just right" is hard.


>> Sorry, I kept typing "asymmetric" when I meant "symmetric". Tired
>> here (new baby at home :-)).
>
> Congratulations!  And congratulations on firefox 3.5 too by the way!

Thanks. Helps to keep things in perspective ;-).


> I'm all for internal properties rather than "this+" and "+this".

How about an alternative spec that does not preclude hidden class  
optimizations based on + overloadings whether by multimethods or "this
+"/"+this"?

I suppose we can't defer the "type" issue, though...


> Relying on a connection between constructor functions and instances is
> fragile, as I mentioned in a previous message to Mark about
> instanceof.  You can make changes to a constructor function that is
> not reflected in existing instances.

Constructor properties don't show up in instances, so I'm not sure why  
this is a problem.


> You can make changes both to
> instances and constructors that disconnect the two completely.

Yes, with extensions such as writable __proto__ -- but that's  
something we are trying to get rid of.

Likewise, for user-defined function foo, foo.prototype is writable --  
but not so for built-in constructor functions, and not so for classes  
as sugar (more below).

Point so far is not to disagree but to cite cases showing a mix of  
higher- and lower-integrity connections between constructors and their  
prototypes.


> Constructor functions are implementation types (as opposed to
> interface or behavior types) which is inherently problematic.  I
> wouldn't make this weak connection the cornerstone of operator
> dispatch.

I suppose Object.freeze could be seen as a "fix" here, but it is  
advisory -- therefore not used when most needed.

The original "classes as sugar" sketch Mark did to general agreement  
at Oslo included freezing the class object (constructor function)  
*and* bind the class name as a const (neither writable nor  
configurable in the binding object or frame).

So a higher-integrity "fix" would be to make classes as proposed for  
Harmony (details still in flux, I think) the cornerstone for operator  
dispatch. I think this is the plan for "value types", based on  
discussion at the last TC39 meeting. Mark will let us know if he  
disagrees.


> The connection between prototypes and instances is a much more direct
> one.  If you change a prototype that change is reflected in the
> instances (modulo shadowing).  If I want my type B to have all the
> same behavior as A I can mix all the methods of A into B's prototype
> and I will in effect have made B a subtype of A.  If operators reside
> in A's prototype, in whatever form, I can do the same for operators.

You could, but your proposal nicely avoided noisy ".prototype"  
expressions all over, as Allen suggested in reply.

Adding Complex, Decimal, Rational, Point, etc. for most users implies,  
nay cries out for being able to use these names without having to  
suffix them with ".prototype". If the binding of the constructor name  
in its object or frame *and* the binding of its 'prototype' property  
are locked down due to how classes as sugar work, then we should be  
able to rely on the constructor / prototype relationship.


> Whether or not A's '+' operator uses 'this', it's still a part of A's
> behavior and the most consistent place to put it is where A's other
> behavior is, its prototype.

Could be, or one could use an optimized multimethod matrix, or some  
other implementation approach. Again I do not see why the spec should  
dictate observable prototype properties, internal or otherwise. If it  
just uses this model but keeps it concealed, then I'm ok with it  
provided we don't close the door on multimethods prematurely.

If we want multimethods, then we might prefer the spec to go a  
different direction and avoid internal "this+"/"+this", etc.

Not sure this helps get a operators (and only operators) proposal  
together. Good discussion, though!

/be

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

Re: Operator overloading revisited

by Maciej Stachowiak :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message


On Jun 28, 2009, at 7:05 AM, 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.  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?

It seems like once you do the defineOperator calls you described  
above, and assuming they work for primitive numbers and not just  
Number objects, then adding two numbers would require two property  
lookups, unless there is an exception for operating on two primitive  
values.

Regards,
Maciej

_______________________________________________
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 think we could get away with making an exception there.  The spec
algorithm for how to execute '+' would check if the operands were
primitive numbers before attempting user-defined operators.

There are already cases where changes to Number.prototype don't
propagate to primitive numbers, leading them to behave differently.
For instance:

Number.prototype.valueOf = function () { return 4; };
print(1 + 1); // yields 2
print(1 + new Number(1)); // yields 5

On Thu, Jul 2, 2009 at 11:53 AM, Maciej Stachowiak<mjs@...> wrote:

>
> On Jun 28, 2009, at 7:05 AM, 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.  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?
>
> It seems like once you do the defineOperator calls you described above, and
> assuming they work for primitive numbers and not just Number objects, then
> adding two numbers would require two property lookups, unless there is an
> exception for operating on two primitive values.
>
> Regards,
> Maciej
>
>
_______________________________________________
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

> Likewise, for user-defined function foo, foo.prototype is writable -- but
> not so for built-in constructor functions, and not so for classes as sugar
> (more below).

All JS code currently in existence is based on user-defined functions.
 For me that is the only case worth considering.  That might explain
the differences in our views.

I'm not a language revolutionary, I prefer gradual evolution.

> The original "classes as sugar" sketch Mark did to general agreement at Oslo
> included freezing the class object (constructor function) *and* bind the
> class name as a const (neither writable nor configurable in the binding
> object or frame).
>
> So a higher-integrity "fix" would be to make classes as proposed for Harmony
> (details still in flux, I think) the cornerstone for operator dispatch. I
> think this is the plan for "value types", based on discussion at the last
> TC39 meeting. Mark will let us know if he disagrees.

If you have the option to use a model that works with the existing
language and that integrates well even (that's subjective of course)
why would you limit it to classes or values?  The reliance of these
new features on each other will just lead them to become a
language-within-a-language, rather than an evolution of the existing
language.

> You could, but your proposal nicely avoided noisy ".prototype" expressions
> all over, as Allen suggested in reply.

That was a typo.

> Adding Complex, Decimal, Rational, Point, etc. for most users implies, nay
> cries out for being able to use these names without having to suffix them
> with ".prototype". If the binding of the constructor name in its object or
> frame *and* the binding of its 'prototype' property are locked down due to
> how classes as sugar work, then we should be able to rely on the constructor
> / prototype relationship.

If people can live with defining their methods as

Foo.prototype.bar = function () { ... }

I think they can live with writing .prototype when defining operators.
 In fact I like the uniformity that you have to use the prototype in
both cases.  Note that library users won't see this, only the library
implementer.  In any case, what users will "cry out for" is
speculation.

>> Whether or not A's '+' operator uses 'this', it's still a part of A's
>> behavior and the most consistent place to put it is where A's other
>> behavior is, its prototype.
>
> Could be, or one could use an optimized multimethod matrix, or some other
> implementation approach. Again I do not see why the spec should dictate
> observable prototype properties, internal or otherwise. If it just uses this
> model but keeps it concealed, then I'm ok with it provided we don't close
> the door on multimethods prematurely.

JS allows introspection and self-modification and both are widely used
and cited as advantages of the language.  I would want to extend that
to cover operators as well.  Having an internal property with some
associated utility methods on Object is a nice way to get that.


-- Christian
_______________________________________________
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 Jul 3, 2009, at 1:21 AM, Christian Plesner Hansen wrote:

Likewise, for user-defined function foo, foo.prototype is writable -- but
not so for built-in constructor functions, and not so for classes as sugar
(more below).

All JS code currently in existence is based on user-defined functions.
For me that is the only case worth considering.  That might explain
the differences in our views.

That's doesn't square with your proposal:

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

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

If you want to amend per Allen's suggestion by imposing fruitless, verbose ".prototype" taxes on the last line, so it reads

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

(or with some other argument order), then what good have you done for users? Unless Point and Number are frozen or otherwise configured in a non-default way, the next line could change one or both .prototype values and make later instances miss the operator.

Sure, that would be a case of "Doctor, it hurts when I do this" (so don't do it), but the same argument applies to instanceof, yet one writes (p instanceof Point), not (p instanceof Point.prototype).

You can't have it both ways. Either mutable .prototype (or mutable function binding, e.g. Point in the global object, or whatever scope it's bound in) is a problem that requires freezing or equivalent lock-down, or it isn't. In any event, there's no reason to impose the .prototype tax, and instanceof-consistency (evolutionary :-P) reason not to.


I'm not a language revolutionary, I prefer gradual evolution.

Your political rhetoric is off target. Classes as sugar are not revolutionary, they build on evolutionary ES3 closures and ES5 Object.freeze and similar features by adding syntactic sugar. They are mostly about "best practices" and "best usability" design defaults for higher-integrity factories than functions called via new can ever be by themselves (due to compatibility).


If you have the option to use a model that works with the existing
language and that integrates well even (that's subjective of course)
why would you limit it to classes or values?

Because we're talking about value types, with that (typeof a == typeof b && a == b) <=> a === b implication extensible to user-defined value types, along with numeric literal syntax.

Value types are new "UI". The benefits won't accrue to user-defined functions as constructors without more opt-in API. And value types deserve first-class syntax, just as classes as sugar do. C# has structs, as Sam pointed out.

Without picking the exact syntax right now, the point remains that we have not been proposing only operators, or even only operators for user-defined constructor functions. We're talking about user-defined value types, ideally with concise, convenient defining as well as invoking syntax.


 The reliance of these
new features on each other will just lead them to become a
language-within-a-language, rather than an evolution of the existing
language.

There are lots of subsets in the language already, this is a teaching and user preference outcome that we can't and shouldn't stop. Multiple paradigms and well-ordered subsets are not a problem, they are a success condition.

But adding operators without considering first-class value types (==/=== and literal syntax) looks like a mistake.


You could, but your proposal nicely avoided noisy ".prototype" expressions
all over, as Allen suggested in reply.

That was a typo.

Why require .prototype noise when instanceof does not? Talk about evolution vs. revolution doesn't justify this break from instanceof's reliance on users to keep their constructor.prototype values intact -- or not, as they please.


If people can live with defining their methods as

Foo.prototype.bar = function () { ... }

That's not the l33t way, of course:

Foo.prototype = {
    bar: function () {...}
    ...
};

But this is noisy and error-prone still, and you are wrong that what one "can live with" should be the last word. People "live with" all sorts of problems in the world, including in JS as it is, and certainly as it was before ES5 (which is definitely not the last word).

Classes as sugar proposes to sweeten such "method definitions", to lighten the boilerplate tax and reduce opportunities for nesting/bracing/"this"-binding errors.


I think they can live with writing .prototype when defining operators.

Your bikeshed is ugly and overlarge. :-P


In fact I like the uniformity that you have to use the prototype in
both cases.

No uniformity with instanceof, is that incipient revolution?

Perhaps just inconsistency for the sake of low-level explicitness, to be as fair as possible. Or possibly pedantry, but again: if user-defined constructor .prototype references are used and neither the constructor binding in its scope object nor the .prototype property are non-writable and non-configurable, then what is the point?

If the "type" name and .prototype *are* locked down (as with classes as sugar), then there's no good reason for .prototype, it's just a "syn-"tax and an unjustified inconsistency with respect to instanceof.


 Note that library users won't see this, only the library
implementer.  In any case, what users will "cry out for" is
speculation.

We're talking about UI now, so user wishes count. And my bikeshed is prettier and smaller -- I invite others (Tucker can recuse himself ;-) to weigh in.

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

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

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

I'm not sold on Dylan's \+ (backslashes are troublesome and overloaded), but like quoting, it is a clean extension to the grammar.


Whether or not A's '+' operator uses 'this', it's still a part of A's
behavior and the most consistent place to put it is where A's other
behavior is, its prototype.

Could be, or one could use an optimized multimethod matrix, or some other
implementation approach. Again I do not see why the spec should dictate
observable prototype properties, internal or otherwise. If it just uses this
model but keeps it concealed, then I'm ok with it provided we don't close
the door on multimethods prematurely.

JS allows introspection and self-modification and both are widely used
and cited as advantages of the language.

Self-modification is widely criticized too, even in this thread. See Allen's measured post on Smalltalk history. Monkey-patching bites back, the ability to do it is a mixed blessing.

I chose to make most things mutable in Netscape 2 (even built-in constructor.prototype, if memory serves) because I had too little time to get the core language right, and I knew users would need to monkey-patch. That was then. As noted earlier in this thread, with ES5's Object.freeze etc., it will become harder over time to count on the ability to monkey-patch.


 I would want to extend that
to cover operators as well.

Why? I'm asking for specific use-cases, e.g. one-off or otherwise ad-hoc operator bindings.

You still haven't addressed the ==/=== relation and other "value type" issues such as literal notation extensibility.


Having an internal property with some
associated utility methods on Object is a nice way to get that.

The internal property is invisible to users of the language, and could be replaced by other implementation techniques. That's why I question it as a spec device.

You really did propose symmetric multimethods, but hooked them up "backstage of the language" using "this+" and "+this", etc. At this stage I contend we should focus on the fundamental design elements including multimethods, and not on the ES1-5-style internal property spec hacks.

And of course prettier, more usable bikesheds are fair game, if anyone wants to comment. Operators and other value type extensibility are motivated by usability problems with functions and methods and numeric literals jammed into strings.

/be

_______________________________________________
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

> There are lots of subsets in the language already, this is a teaching and
> user preference outcome that we can't and shouldn't stop. Multiple paradigms
> and well-ordered subsets are not a problem, they are a success condition.
> But adding operators without considering first-class value types (==/=== and
> literal syntax) looks like a mistake.

This is the critical point.  Do you want operator overloading to
extend to classic objects (that is, instances of user-defined
functions) or to be restricted to the values/classes/types subset?
That's not as much a technical discussion but a question of design
philosophy.  I assumed that integration with the existing language was
desirable.  If not then the proposal is moot.

> No, because for two numbers a and b, a == b <=> a === b. In fact for any two
> values, (typeof a == typeof b && a == b) <=> a === b. If we want to support
> usable numeric and similar "scalar" type extensions, we might want to preserve
> this logic.

(I forgot to respond to this from a previous message)

What does "usable" mean?  If operators did work for classic objects
why not use them for scalars? There's === but I don't know why you
would want it to be anything other than object identity.  Is anybody
relying on (typeof a == typeof b && a == b) <=> a === b?
_______________________________________________
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 Jul 3, 2009, at 5:29 AM, Christian Plesner Hansen wrote:

> This is the critical point.  Do you want operator overloading to
> extend to classic objects (that is, instances of user-defined
> functions) or to be restricted to the values/classes/types subset?
> That's not as much a technical discussion but a question of design
> philosophy.  I assumed that integration with the existing language was
> desirable.  If not then the proposal is moot.

Operators are currently hardcoded in the language, but the  
specification could be recast using multimethods or double dispach and  
then extended to allow users to define operators on new "types" -- but  
what would be the nature of those types?

Anything like a number (primitive, as you note the wrapping with  
Number is an old hack) does not act like a reference (object, typeof x  
== "object") type. It's a value type.

This quest for extensible value types was how TC39 generalized the  
problem statement raised by decimal.


> What does "usable" mean?  If operators did work for classic objects
> why not use them for scalars?

"Usable" takes in operators for new value types, along with literal  
notation. These are aspirations, not obviously out of reach.  
Waldemar's original JS2/ES4 work from ~10 years ago had "units": http://www.mozilla.org/js/language/js20-2002-04/core/lexer.html#units

Since number, boolean, and string are not classic objects the  
generalization from hardcoding does not proceed from "classic objects"  
to novel scalar types, rather from the non-null/void primitive types.

> There's === but I don't know why you
> would want it to be anything other than object identity.  Is anybody
> relying on (typeof a == typeof b && a == b) <=> a === b?

Lots of code on the web happens to depend on this because some folks  
(Doug Crockford recomments this) use === while others use == but  
"usually" with same-typed operands. I think I've even seen some code  
mix == and === usage.

/be
_______________________________________________
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

> Operators are currently hardcoded in the language, but the specification
> could be recast using multimethods or double dispach and then extended to
> allow users to define operators on new "types" -- but what would be the
> nature of those types?
>
> Anything like a number (primitive, as you note the wrapping with Number is
> an old hack) does not act like a reference (object, typeof x == "object")
> type. It's a value type.
>
> This quest for extensible value types was how TC39 generalized the problem
> statement raised by decimal.

If TC39 has decided that value types are the way to go then I agree,
operators have to be considered in that context.  On the other hand if
operator overloading did work for ordinary objects I would suggest
reconsidering whether value types were the simplest way to implement
use cases like decimal.  I'm no fan of having numbers and booleans
hardcoded but at least it's an inconsistency that JS shares with most
other languages.

>> What does "usable" mean?  If operators did work for classic objects
>> why not use them for scalars?
>
> "Usable" takes in operators for new value types, along with literal
> notation. These are aspirations, not obviously out of reach. Waldemar's
> original JS2/ES4 work from ~10 years ago had "units":
> http://www.mozilla.org/js/language/js20-2002-04/core/lexer.html#units

Custom literals is a nasty problem.  To work with decimal literals,
for instance, the compiler can't be allowed to interpret numeric
constants; that has to be delegated to the decimal library.  Value
types would probably make some aspects of it easier (though you have
to watch out if you want to avoid one-pass semantics) but as far as I
can see only the simple aspects.

> Since number, boolean, and string are not classic objects the generalization
> from hardcoding does not proceed from "classic objects" to novel scalar
> types, rather from the non-null/void primitive types.

That's a matter of which angle you want to view it from.  But I take
the point that the same holds for my view.

>> There's === but I don't know why you
>> would want it to be anything other than object identity.  Is anybody
>> relying on (typeof a == typeof b && a == b) <=> a === b?
>
> Lots of code on the web happens to depend on this because some folks (Doug
> Crockford recomments this) use === while others use == but "usually" with
> same-typed operands. I think I've even seen some code mix == and === usage.

I won't try to guess if breaking this relation will give problems in
practice.  If it's really one of the central reasons for having value
types it would be worth looking into.


-- Christian
_______________________________________________
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 Jul 5, 2009, at 7:52 AM, Christian Plesner Hansen wrote:

> Custom literals is a nasty problem.  To work with decimal literals,
> for instance, the compiler can't be allowed to interpret numeric
> constants; that has to be delegated to the decimal library.

To clarify, we wouldn't make 123 or 3.14 be extensible (at least not  
without some "use decimal" pragma, which seems very difficult to  
implement, since it would not affect lexical scope or other compile-
time constructs, but rather have runtime effects). You'd have to write  
1.1m or similar.

/be

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

Re: Operator overloading revisited

by Alex Russell :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message


On Jul 3, 2009, at 1:21 AM, Christian Plesner Hansen wrote:

>> Likewise, for user-defined function foo, foo.prototype is writable  
>> -- but
>> not so for built-in constructor functions, and not so for classes  
>> as sugar
>> (more below).
>
> All JS code currently in existence is based on user-defined functions.
> For me that is the only case worth considering.  That might explain
> the differences in our views.
>
> I'm not a language revolutionary, I prefer gradual evolution.
>
>> The original "classes as sugar" sketch Mark did to general  
>> agreement at Oslo
>> included freezing the class object (constructor function) *and*  
>> bind the
>> class name as a const (neither writable nor configurable in the  
>> binding
>> object or frame).

This point disturbs me. Making classes frozen solves no existing JS  
programmer problems, introduces new restrictions with no mind paid to  
the current (useful) patterns of WRT the prototype chain, and  
introduces the need for a const that only seems there to make some  
weird security use-cases work. And I say that with all sympathy to  
weird language features and the security concerns in question.

Why should this stuff be the default? It's time to admit that built-
ins in JS are weird and that as the weirdos, they deserve to work  
harder to make things happen to/for them, particularly since language  
implementer code size is incomparably cheap compared to script author  
code size.

Questions of "integrity" here to my mind should be justified by why  
they'll be good for ALL code, not just abuse of built-ins, and then  
weighed against other possible solutions. Freezing classes seems  
premature to me.

Regards

_______________________________________________
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, Jul 6, 2009 at 6:10 PM, Alex Russell<alex@...> wrote:

>>> The original "classes as sugar" sketch Mark did to general agreement at
>>> Oslo
>>> included freezing the class object (constructor function) *and* bind the
>>> class name as a const (neither writable nor configurable in the binding
>>> object or frame).
>
> This point disturbs me. Making classes frozen solves no existing JS
> programmer problems, introduces new restrictions with no mind paid to the
> current (useful) patterns of WRT the prototype chain, and introduces the
> need for a const that only seems there to make some weird security use-cases
> work.
> [...]
> Questions of "integrity" here to my mind should be justified by why they'll
> be good for ALL code, not just abuse of built-ins, and then weighed against
> other possible solutions. Freezing classes seems premature to me.


For low integrity patterns (or "loosey goosey" as Allen sometimes
says), JavaScript has always been and will remain a fine language. In
ES5 for the first time, it is possible to engage in high integrity
patterns; but it is not easy. We don't need syntactic sugar to provide
yet another way to express what can already be expressed easily. It is
when something valuable becomes possible, but can't be made easy
without syntactic sugar -- like high integrity programming in ES5 --
that syntactic sugar may be justified.

So no, it doesn't need to be justified for all code. It just needs to
be justified by code that isn't well served by ES5 as it is. No one is
proposing removing JavaScript's support for making low integrity
programming easy.

As for whether high integrity is only of interest to security weirdos,
I'll just remind everyone that the notion of objects [Simula,
Smalltalk, Actors] -- or similarly abstract data types [Clu] -- was
conceived from the beginning as: reusable packages of encapsulated
state together with the code responsible for maintaining that state's
invariants and presenting an abstract interface. That interface that
quickly came to be characterized in terms of pre-conditions and
post-conditions [Liskov].

Virtually none of the systems or literature that brought about this
revolution was concerned with security per se. These notions were
created for the sake of modularity, abstraction, and flexible
composability. None of these concerns have gone away; hence the
frequent complaint that JavaScript today provides inadequate support
for serious large scale programming -- a need which classes-as-sugar
hopes to address.

Finally, I make no apology for being a security weirdo or for wanting
to see JavaScript become more securable. JavaScript is uniquely
positioned as the fastest growing portion of many organizations'
attack surface; and often also their weakest link. This is a role for
which it was not designed and for which it has been ill suited. By the
same token, JavaScript is also uniquely positioned to capitalize on
prior work on programming language security. It's continued evolution
should indeed be shaped by the unique niche it occupies.

--
    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 Jul 6, 2009, at 6:10 PM, Alex Russell wrote:

This point disturbs me. Making classes frozen solves no existing JS programmer problems, introduces new restrictions with no mind paid to the current (useful) patterns of WRT the prototype chain, and introduces the need for a const that only seems there to make some weird security use-cases work. And I say that with all sympathy to weird language features and the security concerns in question.

Why should this stuff be the default?

What Mark said, I agree completely with his post: "this stuff" is *not* "the default" -- function as constructor is the default, and no one is salting that syntax. So why are you against sugar for high-integrity programmers? It's not as if those weirdos will take over the world, right?

If you're worried that the 'class' keyword will hypnotize the masses into locking things down overmuch, then you don't trust the masses very much. They'll mostly avoid new stuff, and any who get burned by inability to monkey-patch (which is *not* an unmixed blessing) will retreat, and probably start a "don't use 'class'" movement.

This is not 'final' in Java (which pace Mark, I believe was overused, including in the standard library, but which also has different enough semantics that I wouldn't drag it in here as a precedent. We're talking about sugar for ES3 and ES5 facilities already implemented, or nearly implemented. There was no such underlying metaprogramming API prefiguring final in Java, AFAIK.

There should be sugar for high- and low-integrity programmers, and then they can get back to their standoff. :-/

/be

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

Re: Operator overloading revisited

by David-Sarah Hopwood-2 :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

Brendan Eich wrote:

> On Jul 6, 2009, at 6:10 PM, Alex Russell wrote:
>
>> This point disturbs me. Making classes frozen solves no existing JS
>> programmer problems, introduces new restrictions with no mind paid to
>> the current (useful) patterns of WRT the prototype chain, and
>> introduces the need for a const that only seems there to make some
>> weird security use-cases work. And I say that with all sympathy to
>> weird language features and the security concerns in question.
>>
>> Why should this stuff be the default?
>
> What Mark said, I agree completely with his post: "this stuff" is *not*
> "the default" -- function as constructor is the default, and no one is
> salting that syntax. So why are you against sugar for high-integrity
> programmers? It's not as if those weirdos will take over the world, right?

We security weirdos fully intend to take over the world.
You have been warned.

--
David-Sarah Hopwood  ⚥  http://davidsarah.livejournal.com

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