Operator overloading for non-value objects

# Anne van Kesteren (6 years ago)

In a discussion I had with Alex Russell as how to do comparison for URL objects it ended up with desiring

url == url2

to work. It escaped me at that point that I already discussed this briefly and Brendan explained why face-to-face. However, I forgot what he said :/

The alternative, either something like

url.equals(url2)

or

URL.equal(url, url2)

or

url.toString() == url2.toString()

is somewhat Java-esque. Is that what we should do? And if so, opinions on which variant?

# Dean Landolt (6 years ago)

Value Objects. See the recent thread re: value objects on es-discuss. Slides from the presentation Brendan linked to (www.slideshare.net/BrendanEich/js-resp) confirm that == will still be overloadable and he says he'll be working on writing it all up this month.

# Anne van Kesteren (6 years ago)

Value objects are immutable. URL objects do not have that quality which is why I titled this thread overloading for non-value objects...

# Claude Pache (6 years ago)

url.toString() == url2.toString()

Experiences on latest versions of Firefox and Safari: url.toString() and url.valueOf() give me "[object URL]". I expected to obtain an equivalent of url.href. I haven't found mention of toString or valueOf in the WHATWG URL spec (url.spec.whatwg.org), although I may have missed the feature. Was there an oversight?

# Anne van Kesteren (6 years ago)

On Mon, Jan 13, 2014 at 1:47 PM, Claude Pache <claude.pache at gmail.com> wrote:

I haven't found mention of toString or valueOf in the WHATWG URL spec (url.spec.whatwg.org), although I may have missed the feature.

Web IDL named it "stringifier". I recommend filing bugs on browsers.

# Dean Landolt (6 years ago)

On Mon, Jan 13, 2014 at 8:38 AM, Anne van Kesteren <annevk at annevk.nl> wrote:

Value objects are immutable. URL objects do not have that quality which is why I titled this thread overloading for non-value objects...

Ah, I apologize -- I didn't bother reading the subject.

I'd only add that in spite of all the legacy, the mutation-heavy API of URL objects seems pretty clunky, especially in the presence of value objects. Ah well.

# Brendan Eich (6 years ago)

Anne van Kesteren wrote:

Value objects are immutable. URL objects do not have that quality which is why I titled this thread overloading for non-value objects...

Then no == for URL objects.

We want to keep the relatively few invariants in the language that we have. Why is it so important to have == for (mutable) URLs?

# Anne van Kesteren (6 years ago)

On Mon, Jan 13, 2014 at 3:40 PM, Brendan Eich <brendan at mozilla.com> wrote:

We want to keep the relatively few invariants in the language that we have. Why is it so important to have == for (mutable) URLs?

The alternative is rather ugly. You don't want to sometimes write == (value objects) and sometimes write .equals() (non-value objects, method name won't always be the same, e.g. we have isEqualNode() already).

I guess for now we should just go with url.equals(url2) then and see where that takes us.

(Alex feels more strongly about this than I do.)

# Brendan Eich (6 years ago)

Anne van Kesteren wrote:

The alternative is rather ugly. You don't want to sometimes write == (value objects) and sometimes write .equals() (non-value objects, method name won't always be the same, e.g. we have isEqualNode() already).

The point is you do want == to mean, for two values, something that's true on the next line and the one three after that, no matter what mutations to the heap might occur (note: I'm not talking about rebinding variables).

At least, some people do. If we can't agree, the conservative-language-design outcome will still not let mutable objects be sometimes==.

I know, other languages allow this. The experience in those languages seems mixed to bad. Anyway, that's my view. What do others think?

# Domenic Denicola (6 years ago)

(Warning: this reply somehow got rather rambly. Sigh.)

From: Brendan Eich <brendan at mozilla.com>

I know, other languages allow this. The experience in those languages seems mixed to bad. Anyway, that's my view. What do others think?

Having used operator overloading in C#, I found it quite useful, even for mutable objects. Overloading == was by far the most common.

In domain-driven design terms, it comes down to the appropriate notion of equality for value objects (should be immutable) vs. entities (often mutable). Conceptually, value objects equal if all their composite parts are equal, whereas entity equality is determined by identity (e.g. same value for their ID properties). Allowing == to be useful for both value objects and entities was, in my experience, a crucial part of making equality comparison a useful part of the language.

The situation is somewhat different in C# than in JavaScript, though. For example, in C#, overloading == would always be accompanied by implementing the appropriate IEquatable stuff, in concordance with your == implementation. This would have ramifications for the various standard library data structures: for example, a set data structure would use the IEquatable implementation to determine uniqueness, or a dictionary would use it for key equality.

In JavaScript, our data structures are specified to use SameValue (or SameValueZero, or Strict Equality Comparison); none of them use a user-overridable hook. Furthermore, years of best-practice advice have made a lot of programmers prefer using === exclusively, ignoring == altogether. I for one would find it uncomfortable to start switching to == on the hope that it does something useful, in case the left-hand side is a value object with a proper overload implementation. (I would of course want to avoid it for the primitives, since its coercion behavior there is rarely desired.)

So ... I guess I am saying, I am not sure that making == work for mutable objects is salvagable. It can be made to work for immutable objects, maybe, assuming that their immutability is also used to modify SameValue et al. and thus allow a consistent behavior between == and e.g. map.has. (I suppose I am asking for invariants x == y && Type(x) is Object && Type(y) is Object implies map.has(x) === map.has(y).) But unless we are prepared to allow the overloaded == to propagate throughout the language, which seems unlikely since the language rarely uses Abstract Equality Comparison, then I can't really see a place for == overloading on mutable objects.

# Brendan Eich (6 years ago)

Thanks for the reply.

Domenic Denicola wrote:

In JavaScript, our data structures are specified to use SameValue (or SameValueZero, or Strict Equality Comparison); none of them use a user-overridable hook. Furthermore, years of best-practice advice have made a lot of programmers prefer using === exclusively, ignoring == altogether. I for one would find it uncomfortable to start switching to == on the hope that it does something useful, in case the left-hand side is a value object with a proper overload implementation. (I would of course want toavoid it for the primitives, since its coercion behavior there is rarely desired.)

Indeed, writing something like

if (urlObj == "http://foo.com") {...}

today could take advantage of toString or valueOf to "overload".

So ... I guess I am saying, I am not sure that making == work for mutable objects is salvagable. It can be made to work for immutable objects, maybe,

definitely

assuming that their immutability is also used to modify SameValue et al. and thus allow a consistent behavior between == and e.g. map.has.

No, that's ===. For value objects, == is completely overloadable.

Of course, a buggy or perverse implementation could give inconsistent results, so my argument against mutability is weaker and relies on best effort/testing. So I'm not totally, unalterably opposed, but I am opposed.

Your point about it being too late to salvage == (vs. ===) is good, but perhaps with value objects plus further work to disable implicit conversions, == will make a come-back -- but that's far down the road.

(I suppose I am asking for invariants x == y&& Type(x) is Object&& Type(y) is Object implies map.has(x) === map.has(y).)

That follows from the x == y && typeof x == typeof y <=> x === y relation already in the language, except for NaNs (which you ignored, so I will too!).

But unless we are prepared to allow the overloaded == to propagate throughout the language, which seems unlikely since the language rarely uses Abstract Equality Comparison, then I can't really see a place for == overloading on mutable objects.

Good argument, thanks for making it.

# Kevin Smith (6 years ago)

Your point about it being too late to salvage == (vs. ===) is good, but perhaps with value objects plus further work to disable implicit conversions, == will make a come-back -- but that's far down the road.

Work to disable implicit conversions? Can you clarify?

I'm actually quite wary (so far) of allowing the user to override an operator whose abstract meaning is already so abstruse.

# Claude Pache (6 years ago)

Consider the following objects:

url, url2, ... : URL objects
location, location2, ... : Location objects
a, a2, .... : HTMLAnchorElement objects

All these objects implement URLUtils according to the WhatWG specs, and are therefore stringified using their href attribute. Now, which one of these equalities should "work" by just comparing the stringification?

url == url2
location == url
location == location2
a == url
a == location
a == a2

For me, I couldn't say. But in any case, my intention is clearer (and not too Java-esque) by writing the following:

a.href == location.href

In the worst case, when I don't know if I have a string or an URLUtils object, I just ensure that at least one member of the equality operator is stringified—and, most importantly, that it is evident from reading my code that one member is stringified:

a.href == url
String(whatever) == url
# Kevin Smith (6 years ago)

In the worst case, when I don't know if I have a string or an URLUtils object, I just ensure that at least one member of the equality operator is stringified—and, most importantly, that it is evident from reading my code that one member is stringified:

a.href == url
String(whatever) == url

Is an equality operator which requires one to remember such details a good, dependable equality operator? Does it really even express a meaningful concept of "equality"?

Attaching more nuance on top of "==" via overloading seems like it will make things worse, not better.

Brendan, can you provide a value class example which includes a user-defined "=="? I'd like to see it in context.

# Andrea Giammarchi (6 years ago)

I think there are cases where the intent of == can be clear such Point2D or Point3D comparison, as well as generic collections.

In this case I am simulating though hasSamecontentOf method what I think would be nice simplifying via ==


function Collection(entries) {
  this.push.apply(this, entries);
}

Collection.prototype = Object.setPrototypeOf(
  {
    constructor: Collection,
    hasSameContentOf: function (collection) {
      return this.every(this._sameContentOf, collection);
    },
    _sameContentOf: function (entry, i) {
      return entry === this[i];
      // or a more generic and unoptimized
      // return -1 < this.indexOf(entry);
    }
  },
  Array.prototype
);

var
  a = new Collection([1, 2, 3]),
  b = new Collection([1, 2, 3]),
  c = new Collection(a)
;

alert(
  a.hasSameContentOf(b) &&
  b.hasSameContentOf(c)
);

In JS world we are "use to" compare via === so that when == is used instead we are usually in "power user land", I don't see any conceptual shenanigans in doing something like above code.

Long story short:

point2Da.x === point2Db.x && point2Da.y === point2Db.y

all over the code, if somehow necessary, would probably not look as nice as pointa == pointb

Being something new not possible in ES5 or ES3, I don't even see side effects or regressions that could affect old code: no oervload of == would be present so we are back to the good old known == meaning.

my 2 cents

# Andrea Giammarchi (6 years ago)

for correctness sake, I forgot to check this.every() after a this.length === collection.length && this.every(...)

# Kevin Smith (6 years ago)

In JS world we are "use to" compare via === so that when == is used instead we are usually in "power user land", I don't see any conceptual shenanigans in doing something like above code.

Only power users use "==" because it has weird conversion semantics that are hard to internalize. "==" doesn't map to any simple relational concept, certainly not "equality":

({}) == "[object Object]"; // true

So if a user is going to overload "==", are they going to maintain those bizarre semantics? If so, then crazy-pants. If not, then it seems like we have introduced a hazard into code that is generic with respect to the types of operands to which "==" is applied. Sometimes crazy conversion semantics, sometimes sensible equality semantics.

I'll stop there, because I don't know the details of the proposal.

# Andrea Giammarchi (6 years ago)

it's transparent for the user and your example assumes nobody overwrote Object.prototype.toString/valueOf

That example is a crazy assumption for many reasons and the correct check would be eventually via toString.call(object), well still hoping nobody changed native toString, of course, but you know what I mean.

If power users would like to compare via == when they are aware of operator overload for used classes then I don't see any problem.

Operators overload worked for power users in many other languages and I think if not abused it can be very handy.

Once again, just my 2 cents

# Kevin Smith (6 years ago)

If power users would like to compare via == when they are aware of operator overload for used classes then I don't see any problem.

If you know the operand types already, then there's no problem. But in cases where you don't control the operand types how will you know the semantics that "==" will provide? What does the following mean?

if (maybeCollection1 == maybeCollection2) { ... }

Operators overload worked for power users in many other languages and I think if not abused it can be very handy.

In general, I agree. But in the specific case of "==" I'm not sure we have a solid foundation to build upon. How do you meaningfully overload a language element that, arguably, carries no useful meaning?

Once again, just my 2 cents

Mine too - we need to wait for a proposal before going any further.

# Brendan Eich (6 years ago)

Kevin Smith wrote:

Work to disable implicit conversions? Can you clarify?

It's a gleam in my eye, at least. Some day we may enable users to choose to get exception, not toString, on myUrl == "haha". Details TBD, and not via valueOf/toString hacking (which doesn't quite work).

I'm actually quite wary (so far) of allowing the user to override an operator whose abstract meaning is already so abstruse.

You've seen the rationale, here it is again:

== is overloadable along with < and <= to cope with unordered values and to enable common safe comparisons, e.g. 0m == 0.

!= and ! cannot be overloaded, to preserve De Morgan's Laws and other obvious invariants.

There is no "abstruseness" in x == y when typeof-types match. Even when they don't, for numeric types, the relationals and == are loose. We won't change that. Adding value objects should afford new numeric types the same expressiveness that number has, or usability impairments will hamper adoption.

If it's ok to test !0 or even 0 == "0" (yes, I know == is not transitive in full), then 0m == 0 or 0n == 0 should be supported too.

# Domenic Denicola (6 years ago)

JavaScript is in a sticky place, it seems. === should mean strict equality (modulo -0/+0 and NaN, unfortunately), so just like 0 !== "0", we want 0 !== 0m. That is, === won't be right for comparing numeric types, once we have multiple numeric types.

However, == already means way-too-sloppy equality: 0 == "0". Thus, even if we get operator overloading to give us 0 == 0m, its usefulness is reduced, since we can't trust == unless we know both types are "compatible" (e.g. both numeric, or both immutable URL instances). An operator that is sometimes annoyingly sloppy, and sometimes usefully lenient, is going to be hard to use ("power users only").

What we're missing is the equality operator most languages have: neither too strict, nor too sloppy. One that gives (0 op 0m) === true, but (0 op "0") === false.

This could be achieved, I suppose, by allowing overriding of ===. But is it OK for 0 === 0m? Probably not, right? Especially since 0m is a value "object", and we try to let === applied to objects mean reference equality.

So we're kind of screwed. We could always 927 it, and invent a new operator with the most useful semantics. (The return of is? ====? =dwim=?) Alongside ==, ===, SameValue, and SameValueZero, that'd make 5 notions of equality in ES. Seems bad. I come back to my "kind of screwed" thesis.

# Kevin Smith (6 years ago)

== is overloadable along with < and <= to cope with unordered values and to enable common safe comparisons, e.g. 0m == 0.

What does 0m === 0 mean?

# Domenic Denicola (6 years ago)

0m is literal syntax, for decimal(0). Where decimal is a value type factory.

www.slideshare.net/BrendanEich/web-futures/23

# Kevin Smith (6 years ago)

0m is literal syntax, for decimal(0). Where decimal is a value type factory.

Right - I meant what are the semantics of "===" applied to dissimilar, perhaps "numeric", value types.

# Brendan Eich (6 years ago)

Kevin Smith wrote:

Right - I meant what are the semantics of "===" applied to dissimilar, perhaps "numeric", value types.

We worked through this in 2008 when IBM was pushing decimal at ES3.1 (now ES5). We do not want 0m === 0 for many reasons, including the relation

typeof x == typeof y && x == y <=>  x === y

We also didn't want problems of cohorts differing by significance (1.0m vs. 1m, if you can believe it) being equated to 1 (the number).

This is one of the reasons I've designed value objects (so far) to allow typeof customization. (The other is because typeof is useful in numeric contexts, and making all non-number numerics have typeof-type "object" is useless, a disservice to users.)

There is nothing wrong in my view with well-written == usage. I'm not Crock. I don't say always use ===. While opinions vary, the fact remains that == and <= are in the language, are loose, and need to be overloadable for useful value objects, specifically more numeric types. Suggest you pull == out of your mental penalty box and look at it again. It's not a recidivist.

# Andrea Giammarchi (6 years ago)

if you are not sure and you expect == to act like === within objects / types then just use === and leave == for checks behind the typoef as Brendan said.

Once again, let power users have the control they'd like to, specifications should not prevent people from shooting their foot when/if they want/need to ;-) it's not a spec matter, still IMO

# Kevin Smith (6 years ago)

While opinions vary, the fact remains that == and <= are in the language, are loose, and need to be overloadable for useful value objects, specifically more numeric types.

I agree, that must be a goal of the design.

Suggest you pull == out of your mental penalty box and look at it again.

It's not a recidivist.

Says the one trying to let him out!

: )

# Brendan Eich (6 years ago)

Says the one trying to let him out!

www.hulu.com/watch/12740

Okay then!

# Brendan Eich (6 years ago)

Kevin Smith wrote:

I'll stop there, because I don't know the details of the proposal.

Allowing value objects (and perhaps mutable object) to define operators means giving up some invariants, similar to proxies which can have incoherent has/get etc.

So I'm not sure what you are waiting for. A badly written user-defined value object? Those may be easy to cough up in order to complain about extensibility, but the whole point of value objects is to avoid hardcoding.

If you want an int64 spec/impl to evaluate, that'll take a bit longer. Preview:

js> 0L == 0

true js> 0L === 0

false js> typeof 0L "int64" js> 0UL == 0

true js> 0UL === 0

false js> 0UL == 0L

true js> 0UL === 0L

false js> typeof 0UL "uint64"

Someone who insists on using === only can convert from one numeric type to another:

js> int64(0) === 0L

true

Of course you have to beware of values that convert lossily, but for 0, 1, ... N, we have == and I think it ought to work.

# Kevin Smith (6 years ago)

On Tue, Jan 14, 2014 at 5:44 PM, Brendan Eich <brendan at mozilla.com> wrote:

Kevin Smith wrote:

I'll stop there, because I don't know the details of the proposal.

Allowing value objects (and perhaps mutable object) to define operators means giving up some invariants, similar to proxies which can have incoherent has/get etc.

So I'm not sure what you are waiting for. A badly written user-defined value object? Those may be easy to cough up in order to complain about extensibility, but the whole point of value objects is to avoid hardcoding.

If you want an int64 spec/impl to evaluate, that'll take a bit longer. Preview:

js> 0L == 0 true js> 0L === 0 false js> typeof 0L "int64" js> 0UL == 0 true js> 0UL === 0 false js> 0UL == 0L true js> 0UL === 0L false js> typeof 0UL "uint64"

What about:

0L == "0"

How is "===" defined for user-defined value types? In terms of the T == T overload?

value class x {
    x == x () { ... }
}
# Brendan Eich (6 years ago)

Kevin Smith wrote:

What about:

0L == "0"

So glad you asked, and you will like the answer:

js> 0L == "0"

typein:2:0 TypeError: no operator function found for ==

See image.slidesharecdn.com/jsresp-130914140214-phpapp02/95/slide-12-638.jpg?1379296961,

For the expression v + u

  1. Let p = v.[Get]
  2. If p is not a Set, throw a TypeError
  3. Let q = u.[Get]
  4. If q is not a Set, throw a TypeError
  5. Let r = p intersect q
  6. If r.size != 1 throw a TypeError
  7. Let f = r[0]; if f is not a function, throw
  8. Evaluate f(v, u) and return the result

If v or u is a string, no handed-operator-symbol-named set on prototype chain.

How is "===" defined for user-defined value types? In terms of the T == T overload?

You did not read image.slidesharecdn.com/jsresp-130914140214-phpapp02/95/slide-9-638.jpg?1379296961 :-(.

Strict Equality Operators

  • The strict equality operators, === and !==, cannot be overloaded
  • They work on frozen-by-definition value objects via a structural recursive strict equality test (beware, NaN !== NaN)
  • Same-object-reference remains a fast-path optimization
# Kevin Smith (6 years ago)

So glad you asked, and you will like the answer:

js> 0L == "0" typein:2:0 TypeError: no operator function found for ==

...and..I..do : )

goo.gl/N5txLJ

# Domenic Denicola (6 years ago)

From: es-discuss [mailto:es-discuss-bounces at mozilla.org] On Behalf Of Brendan Eich

js> 0L == "0" typein:2:0 TypeError: no operator function found for ==

Hmm, upon seeing this in action, I'm not sure how I feel about == throwing. It doesn't normally do that (modulo bad custom valueOf/toString methods), so I think there will be a lot of code that assumes when it ==s two values it doesn't need to wrap that expression in try/catch.

do { try { x == y } catch { false } }, the new x == y?

# Brendan Eich (6 years ago)

Domenic Denicola wrote:

From: es-discuss [mailto:es-discuss-bounces at mozilla.org] On Behalf Of Brendan Eich

js> 0L == "0" typein:2:0 TypeError: no operator function found for ==

Hmm, upon seeing this in action, I'm not sure how I feel about == throwing.

Oh for crying out loud!

half a :-)

You can't please anyone around here...

It doesn't normally do that (modulo bad custom valueOf/toString methods), so I think there will be a lot of code that assumes when it ==s two values it doesn't need to wrap that expression in try/catch.

do { try { x == y } catch { false } }, the new x == y?

So what? Exceptions for the insane (intransitive, lossy) == cases are better than toString/valueOf.

JS is damned if it does, and damned if it doesn't.

Anyway, value objects are not dispatching to toString or valueOf when used with arithmetic operators. That's the design. Given == and <=, the rest follows.

# Domenic Denicola (6 years ago)

Heh, yes, damned if you do, etc. etc. I was trying to think up a practical example where this would cause problems (e.g. in CSS libraries strings and numbers often mix), but all my cases involved an ==-using third party library, in which case you'd just pass it Number(myLong) instead of myLong directly and move on with your life.

# Brendan Eich (6 years ago)

Domenic Denicola wrote:

Heh, yes, damned if you do, etc. etc. I was trying to think up a practical example where this would cause problems (e.g. in CSS libraries strings and numbers often mix), but all my cases involved an ==-using third party library, in which case you'd just pass it Number(myLong) instead of myLong directly and move on with your life.

Wait, number works:

js> 0L == 0

true js> 0L + 1

1L

Are you thinking of string?

# Domenic Denicola (6 years ago)

I was thinking of

function thirdPartyLib(val) {
  return val == "0";
}

thirdPartyLib(myLong); // exception
thirdPartyLib(Number(myLong)); // works
# Brendan Eich (6 years ago)

That code deserves what it gets, good and hard. Including exceptions.

# Sebastian Markbåge (6 years ago)

At risk of derailing the conversation with a tangent... I don't understand the premise. Why is it so important that URLs are mutable? (Other than already being drafted that way.)

We're seeing major performance and reliably improvements by going for more immutable types in both our server and client apps. The React library relies heavily on the properties of immutability.

In fact, we even want to prevent our developers from using existing mutable APIs that are known to be error prone. Primarily Date which is a terrible mutable API.

We would like to see more DOM and ES APIs embrace immutability. In the future we would also like to propose more convenience APIs, performance improvements and possibly syntax for making immutable data (such as persistent data structures) easier to work with.

# Anne van Kesteren (6 years ago)

On Wed, Jan 15, 2014 at 7:35 AM, Sebastian Markbåge <sebastian at calyptus.eu> wrote:

At risk of derailing the conversation with a tangent... I don't understand the premise. Why is it so important that URLs are mutable? (Other than already being drafted that way.)

That's a good question. I mostly took after <a> / Location and what existing URL libraries offered, and nobody has questioned that design much to date. It is implemented now in Firefox/Chrome though.

URLSearchParams is mutable and that's definitely desired. So if we still want to expose equivalent functionality we'd have to provide a readonly variant of that.

We could at some point provide a URLValue API maybe for immutable URLs (and have operator overloading).

# Tristan Zajonc (6 years ago)

Continuing the tangent. There are lots of other use cases for operator overloading with mutable objects. On this list, I previously discussed the desire for operator overloading on large mutable matrices. The lack of operator overloading is the biggest syntax annoyance for building numerics libraries in JS. I actually don't have much need for all new numeric value types or literal syntax, but that's just me.

I also pushed a bit for additional operators, to support elementwise/objectwise manipulation of matrices ala Matlab/Julia. The conclusion was that a colon postfix might be feasible: a +: b. I understand this may not be of interest to traditional users of JavaScript, but it would make it possible to have really beautiful numerics libraries and perhaps should be considered. See the thread "elementwise operators" for the rationale, with reference to PEP225 ( www.python.org/dev/peps/pep-0225).