Operator overloading for non-value objects
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.
Value objects are immutable. URL objects do not have that quality which is why I titled this thread overloading for non-value objects...
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?
On Mon, Jan 13, 2014 at 1:47 PM, Claude Pache <claude.pache at gmail.com> wrote:
I haven't found mention of
toString
orvalueOf
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.
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.
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?
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.)
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 haveisEqualNode()
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?
(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.
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
impliesmap.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.
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.
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
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.
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
for correctness sake, I forgot to check this.every()
after a this.length === collection.length && this.every(...)
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.
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
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.
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.
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.
== 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?
0m
is literal syntax, for decimal(0)
. Where decimal
is a value type factory.
0m
is literal syntax, fordecimal(0)
. Wheredecimal
is a value type factory.
Right - I meant what are the semantics of "===" applied to dissimilar, perhaps "numeric", value types.
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.
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
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!
: )
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.
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 () { ... }
}
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
- Let p = v.[Get]
- If p is not a Set, throw a TypeError
- Let q = u.[Get]
- If q is not a Set, throw a TypeError
- Let r = p intersect q
- If r.size != 1 throw a TypeError
- Let f = r[0]; if f is not a function, throw
- 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
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 : )
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
?
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 intry
/catch
.
do { try { x == y } catch { false } }
, the newx == 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.
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.
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 itNumber(myLong)
instead ofmyLong
directly and move on with your life.
Wait, number works:
js> 0L == 0
true js> 0L + 1
1L
Are you thinking of string?
I was thinking of
function thirdPartyLib(val) {
return val == "0";
}
thirdPartyLib(myLong); // exception
thirdPartyLib(Number(myLong)); // works
That code deserves what it gets, good and hard. Including exceptions.
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.
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).
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).
In a discussion I had with Alex Russell as how to do comparison for URL objects it ended up with desiring
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
or
or
is somewhat Java-esque. Is that what we should do? And if so, opinions on which variant?