September 05, 2015
On 2015-09-05 08:26, Jonathan M Davis wrote:

> Clearly, you haven't read TDPL recently enough. ;)
>
> There is a free function, opEquals, in object.d which gets called for
> classes, and _it_ is what == gets translated to for classes, and it
> calls the member function version of opEquals on classes:
>
> https://github.com/D-Programming-Language/druntime/blob/master/src/object.d#L143
>
>
> This allows us to avoid a number of fun bugs with opEquals that you get
> in languages like Java and makes it completely unnecessary to do stuff
> like check whether the argument to opEquals is null. Timon gave the link
> to the explanation in the spec:

Bu you don't see my example as a problem?

-- 
/Jacob Carlborg
September 07, 2015
On 09/05/2015 08:18 AM, Jonathan M Davis wrote:
> On Friday, 4 September 2015 at 20:39:14 UTC, Timon Gehr wrote:
>> On 09/04/2015 09:21 PM, H. S. Teoh via Digitalmars-d wrote:
>>>
>>> Wait, wait, did I miss something? Since when was operator overloading
>>> allowed as free functions?
>>
>> Since UFCS, but DMD does not implement it.
>
> There is nothing in the spec about supporting operator overloading with
> free functions, so I don't know where you get the idea that it's even
> intended to be a feature. UFCS applies to functions which use the member
> function call syntax, and operators aren't used that way.

Specifying semantics via lowering is somewhat pointless if rewrites are not transitive.

> There is no plan whatsoever to support operator overloading via free functions.
> ...

Then specify operator overloading using __traits(getMember,...). (I consider this unwise though.)
September 08, 2015
On Saturday, 5 September 2015 at 09:45:36 UTC, Jacob Carlborg wrote:
> On 2015-09-05 08:26, Jonathan M Davis wrote:
>
>> Clearly, you haven't read TDPL recently enough. ;)
>>
>> There is a free function, opEquals, in object.d which gets called for
>> classes, and _it_ is what == gets translated to for classes, and it
>> calls the member function version of opEquals on classes:
>>
>> https://github.com/D-Programming-Language/druntime/blob/master/src/object.d#L143
>>
>>
>> This allows us to avoid a number of fun bugs with opEquals that you get
>> in languages like Java and makes it completely unnecessary to do stuff
>> like check whether the argument to opEquals is null. Timon gave the link
>> to the explanation in the spec:
>
> Bu you don't see my example as a problem?

Well, it might be a bit annoying, but it's simply a matter of adjusting your code to call opEquals explicitly when trying to call the base version, whereas without the free function opEquals, you have subtle correctness problems. For instance, if you have base == derived and derived == base, you'll get the same result for both for D, whereas the equivalent Java or C# could would likely not, because the free function opEquals checks both lhs.opEquals(rhs) and rhs.OpEquals(lhs) whether you did base == derived or derived == base.

So, while what we have is by no means perfect, I think that it is an improvement over what Java and C# did.

- Jonathan M Davis
September 08, 2015
On Monday, 7 September 2015 at 10:26:00 UTC, Timon Gehr wrote:
> On 09/05/2015 08:18 AM, Jonathan M Davis wrote:
>> On Friday, 4 September 2015 at 20:39:14 UTC, Timon Gehr wrote:
>>> On 09/04/2015 09:21 PM, H. S. Teoh via Digitalmars-d wrote:
>>>>
>>>> Wait, wait, did I miss something? Since when was operator overloading
>>>> allowed as free functions?
>>>
>>> Since UFCS, but DMD does not implement it.
>>
>> There is nothing in the spec about supporting operator overloading with
>> free functions, so I don't know where you get the idea that it's even
>> intended to be a feature. UFCS applies to functions which use the member
>> function call syntax, and operators aren't used that way.
>
> Specifying semantics via lowering is somewhat pointless if rewrites are not transitive.

Specifying semantics via lowering makes the compiler simpler and the expected behavior easier to understand. Nothing about that requires that it transitively apply all syntactic sugar, and UFCS is simply syntactic sugar. Sure, it _could_ be implemented that way, but the only reason I see to do that is if we're specifically looking to support defining overloaded operators outside of the types that they apply to. I can't think of anything else that would be affected by it.

Regardless, I honestly think that it would be a very bad technical decision to support defining overloaded operators outside of the type itself - _especially_ when you take into account operators that have defaults generated by the compiler (e.g. opEquals), since that would allow a third party to change what your code does by adding their own overloaded operator. And IIRC, Walter has explicitly stated that it's purposeful that you cannot define overloaded operators outside of the struct/class that they're for. So, anyone who wants it to be otherwise is going to have to convince him.

- Jonathan M Davis
September 08, 2015
On Thursday, 3 September 2015 at 06:37:20 UTC, Jacob Carlborg wrote:
> On 2015-09-02 22:25, w0rp wrote:
>
>> Yeah, I would just call super.opEquals, like so.
>
> I know that's the workaround, but the question is if it's a good implementation/behavior of opEquals.

Strictly speaking OO equality ought to yield three outcomes: yes, no, maybe.

You can't really tell if an instance of Cat and and instance of Animal are equal based on features. It would be better to project an aspect of the instance and compare that instead ("equally powerful" etc).

September 08, 2015
On 09/08/2015 06:49 PM, Jonathan M Davis wrote:
> On Monday, 7 September 2015 at 10:26:00 UTC, Timon Gehr wrote:
>> On 09/05/2015 08:18 AM, Jonathan M Davis wrote:
>>> On Friday, 4 September 2015 at 20:39:14 UTC, Timon Gehr wrote:
>>>> On 09/04/2015 09:21 PM, H. S. Teoh via Digitalmars-d wrote:
>>>>>
>>>>> Wait, wait, did I miss something? Since when was operator overloading
>>>>> allowed as free functions?
>>>>
>>>> Since UFCS, but DMD does not implement it.
>>>
>>> There is nothing in the spec about supporting operator overloading with
>>> free functions, so I don't know where you get the idea that it's even
>>> intended to be a feature. UFCS applies to functions which use the member
>>> function call syntax, and operators aren't used that way.
>>
>> Specifying semantics via lowering is somewhat pointless if rewrites
>> are not transitive.
>
> Specifying semantics via lowering makes the compiler simpler

Not necessarily. The compiler can do lowering (even using internal syntax tree nodes that have no D syntax equivalent!) no matter how the behaviour is specified. It's just easier to come up with the required compiler code and to verify it if the specification does it this way.

> and the
> expected behavior easier to understand. Nothing about that  requires that
> it transitively apply all syntactic sugar, and UFCS is simply syntactic
> sugar.


All about that requires that it applies all rewrites transitively. It is the entire point that the lowered version is again plain D code. (Also, UFCS is not "simply syntactic sugar". There are special lookup rules. Operator overloading ought to be the simple syntactic sugar here, but it isn't, because built-in types don't define the respective operator overloading functions. They should.)

> Sure, it _could_ be implemented that way, but the only reason I
> see to do that is if we're specifically looking to support defining
> overloaded operators outside of the types that they apply to. I can't
> think of anything else that would be affected by it.
> ...

The compiler does not match the specification. I see no reason to change the specification here, but it would be easy.

> Regardless, I honestly think that it would be a very bad technical
> decision to support defining overloaded operators outside of the type
> itself - _especially_ when you take into account operators that have
> defaults generated by the compiler (e.g. opEquals), since that would
> allow a third party to change what your code does by adding their own
> overloaded operator.

Well, how? "Overloaded operators" are just specially named functions that support an additional call syntax. There are no strange issues with modularity that somehow only apply to overloaded operators. UFCS calls can never ignore methods of the type. It does not matter how they were generated. Was this your strongest point against having the compiler combine UFCS and operator call syntax in the straightforward fashion?

September 08, 2015
On Tuesday, 8 September 2015 at 20:55:35 UTC, Timon Gehr wrote:
> On 09/08/2015 06:49 PM, Jonathan M Davis wrote:
>> Sure, it _could_ be implemented that way, but the only reason I
>> see to do that is if we're specifically looking to support defining
>> overloaded operators outside of the types that they apply to. I can't
>> think of anything else that would be affected by it.
>> ...
>
> The compiler does not match the specification. I see no reason to change the specification here, but it would be easy.

I don't know where you get this idea. The spec says _nothing_ about UFCS applying to overloaded operators or that UFCS would apply to lowered code in any way shape or form.

>> Regardless, I honestly think that it would be a very bad technical
>> decision to support defining overloaded operators outside of the type
>> itself - _especially_ when you take into account operators that have
>> defaults generated by the compiler (e.g. opEquals), since that would
>> allow a third party to change what your code does by adding their own
>> overloaded operator.
>
> Well, how? "Overloaded operators" are just specially named functions that support an additional call syntax. There are no strange issues with modularity that somehow only apply to overloaded operators. UFCS calls can never ignore methods of the type. It does not matter how they were generated. Was this your strongest point against having the compiler combine UFCS and operator call syntax in the straightforward fashion?

There would be no way to disambiguate overloaded operators if an operator were overloaded in multiple modules. foo + bar has no import paths involved in it at all. So, what would you do, write opBinary!"+"(foo, bar) instead? That's downright hideous, and it relies on you using the function call to emulate the operator correctly. For something like bar++, you'd be even more screwed, because it doesn't lower to a simple function call.

UFCS is already enough of a problem on its own. It has some benefits, but it doesn't work when conflicts come into play, forcing you to just call the function normally, and its overload rules are such that it doesn't actually prevent hijacking in all cases (e.g. your code could suddenly change behavior, because you used UFCS with a type that then had a function with the same name and parameters added to it). And overloaded operators are closely tied to what a type does, whereas functions are far more diverse. So, there's a lot more to be gained with UFCS than with declaring overloaded operators separately from a type. I really don't see any reason why it would even make sense to declare operators separately from a type. So that you can declare multiple overloads of it for when you import different modules? That would just be plain confusing and incredibly error-prone. And if the problem is that you're dealing with someone else's type, then just declare a function to do what you want and be done with it. And do you really want people to be able to overload stray operators for stuff like strings and then write code that uses + and - and / or whatever on them? That would be ludicrous. It's one thing for people to do that with their own types. It's quite another to do that to built-in types or to someone else's types. What do you want next? To be able to declare constructors for someone else's type? That kind of stuff is part of the type, and allowing 3rd parties to declare it as well just makes it that much harder to figure out what's going on. At least when using UFCS, it's the exception that the function is on the type, so the overload rules don't usually shoot you in the foot (though they can), and you know to look elsewhere for the function just like you would with a function that was called normally. It's the complete opposite with operators.

We have overloaded operators so that someone who is writing a user-defined type can make it act like a built-in type where appropriate. Allowing 3rd party code to declare overloaded operators for a type doesn't help with that at all.

There is _nothing_ in the spec which supports UFCS having anything to do with overloaded operators and nothing to support overloading operators separately from a type. And I recall Walter Bright stating that it was on purpose that you can only overload operators on a type. So, even if the spec _could_ be interpreted to mean that you should be able to declare overloaded operators separately from the type that they apply to, that's _not_ its intention, and you're going to have to convince Walter if you want anything else.

- Jonathan M Davis
September 09, 2015
On 2015-09-08 18:40, Jonathan M Davis wrote:

> Well, it might be a bit annoying, but it's simply a matter of adjusting
> your code to call opEquals explicitly when trying to call the base
> version

Given that fact that I found this problem in std.xml.Document shows either that this is not so easy to remember or that std.xml is basically never used and it doesn't have a test for this. That it doesn't have a test is true regardless.

> ,whereas without the free function opEquals, you have subtle
> correctness problems. For instance, if you have base == derived and
> derived == base, you'll get the same result for both for D, whereas the
> equivalent Java or C# could would likely not, because the free function
> opEquals checks both lhs.opEquals(rhs) and rhs.OpEquals(lhs) whether you
> did base == derived or derived == base.
>
> So, while what we have is by no means perfect, I think that it is an
> improvement over what Java and C# did.

It should be possible for the compiler to see that it's a super call and generate a call to opSuperEquals (or similar) instead, which would make a non-virtual call when calling opEquals on the instance.

-- 
/Jacob Carlborg
September 09, 2015
On 09/09/2015 01:32 AM, Jonathan M Davis wrote:
> On Tuesday, 8 September 2015 at 20:55:35 UTC, Timon Gehr wrote:
>> On 09/08/2015 06:49 PM, Jonathan M Davis wrote:
>>> Sure, it _could_ be implemented that way, but the only reason I
>>> see to do that is if we're specifically looking to support defining
>>> overloaded operators outside of the types that they apply to. I can't
>>> think of anything else that would be affected by it.
>>> ...
>>
>> The compiler does not match the specification. I see no reason to
>> change the specification here, but it would be easy.
>
> I don't know where you get this idea.

Lowering transforms D code to other D code.

See http://dlang.org/operatoroverloading.html :

We have that e.g. "-e" gets rewritten to "e.opUnary!("-")()".
"e.opUnary!("-")()" is D code. To specify the semantics implemented in DMD, the rewrite should be __traits(getMember,e,"opUnary")!("-")() (assuming that the parser is fixed so it can actually parse this, which would be useful in its own right.)

>>> Regardless, I honestly think that it would be a very bad technical
>>> decision to support defining overloaded operators outside of the type
>>> itself - _especially_ when you take into account operators that have
>>> defaults generated by the compiler (e.g. opEquals), since that would
>>> allow a third party to change what your code does by adding their own
>>> overloaded operator.
>>
>> Well, how? "Overloaded operators" are just specially named functions
>> that support an additional call syntax. There are no strange issues
>> with modularity that somehow only apply to overloaded operators. ...
>
> There would be no way to disambiguate overloaded operators if an
> operator were overloaded in multiple modules. ...
> ...

Yes, there would be. Just use aliases. (Also, overloaded operators are not actually that likely to cause conflicts, because they are best written to match a suitably constrained set of types, just like other functions.)

> UFCS is already enough of a problem on its own. It has some benefits,
> but it doesn't work when conflicts come into play, forcing you to just
> call the function normally,

No, it does not force you to do that.

> and its overload rules are such that it
> doesn't actually prevent hijacking in all cases (e.g. your code could
> suddenly change behavior, because you used UFCS with a type that then
> had a function with the same name and parameters added to it).

I think that's universally understood, but I don't see how this relates to the issue at hand.

> And
> overloaded operators are closely tied to what a type does, whereas
> functions are far more diverse.  So, there's a lot more to be gained with
> UFCS than with declaring overloaded operators separately from a type.

UFCS and this ad-hoc terminology do not describe separate things.

> So that you can declare multiple
> overloads of it for when you import different modules? That would just
> be plain confusing and incredibly error-prone.

(Straw man.)

> And if the problem is
> that you're dealing with someone else's type, then just declare a
> function to do what you want and be done with it.

I agree with this part, overloaded operators are just such functions.

> And do you really want
> people to be able to overload stray operators for stuff like strings and
> then write code that uses + and - and / or whatever on them? That would
> be ludicrous. It's one thing for people to do that with their own types.
> It's quite another to do that to built-in types or to someone else's
> types.

This is not distinct from being able to declare functions operating on built-in types or someone else's types that have other unsuitable names. Also, in case you missed it, I also briefly promoted the idea that built-in types should define the operator overloading functions in order to make treatment of operators uniform (as far as the specification is concerned, the compiler could just rewrite the special members to whatever internal representation it uses now for the built-in types). In particular, "+", "-" and "/" can't be overloaded externally on strings in such a setting.

> What do you want next?

(That's a nice way of introducing a straw man.)

> To be able to declare constructors for someone else's type?

That's not even close to being similar.

> That kind of stuff

:o)

AFAICT, they were just clustered together randomly in order to artificially boost the argument.

> is part of the type, and
> allowing 3rd parties to declare it as well just makes it that much
> harder to figure out what's going on. At least when using UFCS, it's the
> exception that the function is on the type, so the overload rules don't
> usually shoot you in the foot (though they can), and you know to look
> elsewhere for the function just like you would with a function that was
> called normally. It's the complete opposite with operators.
> ...

I don't see where you get this idea. It should be noted that operator overloading isn't even that common and basically all legitimate usages are obvious. (They are those cases where a operator is the most natural name for a function.)

> We have overloaded operators so that someone who is writing a
> user-defined type can make it act like a built-in type where
> appropriate. Allowing 3rd party code to declare overloaded operators for
> a type doesn't help with that at all.
> ...

On 09/09/2015 01:32 AM, Jonathan M Davis wrote: (moved from above)
> I really don't see any reason why it would even make sense to declare
> operators separately from a type.

One reason is that single dispatch can be awkward. A textbook example would be: you have a set of special matrix types (dense, sparse, diagonal, triangular, column-first, row-first, etc.), and now want to implement multiplication efficiently for all pairs of types via static dispatch. Why should I have to jump through hoops just to be able to use suitable function names that are associated with the wanted call syntax?

Another reason is that it is annoying that overloaded operators choose the calling convention for you, asymmetrically only for the first argument.

It certainly shouldn't be hard to produce more of those examples. Non-orthogonal features tend to cause subtle (and less subtle) pain points.


It would be better already if UFCS only worked for overloaded operators if the type and the operator where defined in the same module, but I really don't see the point of adding arbitrary limitations to the language. There might be legitimate use cases.

> There is _nothing_ in the spec which supports UFCS having anything to do
> with overloaded operators

On 09/09/2015 01:32 AM, Jonathan M Davis wrote: (moved from the beginning of the post)
> The spec says _nothing_ about UFCS
> applying to overloaded operators or that UFCS would apply to lowered
> code in any way shape or form.
> ...
>

Please stop palindrome-posting. (This is a general tendency in your posts, I wouldn't have brought it up otherwise: they (approximately) start with topic A, go to topic B, then C, return to B and then finish with A. Together with the missing emphasis on conciseness, it can get a little bit tiring at times.)

If you read http://dlang.org/function.html#pseudo-member carefully, nowhere is it explicitly mentioned that it works with built-in types or structs, or with classes named "NoUFCS". Would you also claim that the intention of the specification to support UFCS for those types is questionable? Your argument seems to apply equally well to those cases.

> and nothing to support overloading operators
> separately from a type. And I recall Walter Bright stating that it was
> on purpose that you can only overload operators on a type. So, even if
> the spec _could_ be interpreted to mean that you should be able to
> declare overloaded operators separately from the type that they apply
> to, that's _not_ its intention, and you're going to have to convince
> Walter if you want anything else.
>...

I might do that eventually, but there are more important things to consider at this point.

September 09, 2015
On Saturday, 5 September 2015 at 09:44:13 UTC, Jacob Carlborg wrote:
> On 2015-09-05 08:18, Jonathan M Davis wrote:
>
>> There is nothing in the spec about supporting operator overloading with
>> free functions, so I don't know where you get the idea that it's even
>> intended to be a feature. UFCS applies to functions which use the member
>> function call syntax, and operators aren't used that way. There is no
>> plan whatsoever to support operator overloading via free functions.
>
> Since "a == b" would be lowered to "a.opEquals(b)" one could argue that the compile would also try UFCS since it would do that if the code had been "a.opEquals(b)" from the beginning.

The voice of reason.