February 12, 2019
On Tuesday, 12 February 2019 at 12:35:50 UTC, Jonathan M Davis wrote:
> On Tuesday, February 12, 2019 4:35:28 AM MST Nicholas Wilson
>> Link?
>
> He's said as much on several occasions over the years, but here's a link to one such comment:
>
> https://forum.dlang.org/post/nsffdd$1h6m$1@digitalmars.com

Thanks

> The limitations are deliberate based on the idea that comparison operators need to be consistent and predictable, and should have a close relationship to the mathematical meaning of the operators. Overloading <= to mean something other than "less than or equal" is considered poor style in D, and the same goes for the other arithmetic operators.

I don't think that sentiment changes under the use of <= for symbolic less-than as opposed to logical less-than.

> The use of them to create DSLs (a technique called "expression templates"
> in C++) is discouraged in D, for several reasons. The recommended way to create DSLs in D is to parse strings using CTFE. An excellent example of that is the std.regex package.

Regex fundamentally deals with strings so is a poor choice for comparison (i.e. strings is the right way to do it for regexen, but not for everything). My use case is "give me a form of code that is symbolically the same that I can execute later".

> There are no plans to change this.

Oh well, more things to discuss at dconf. That whole thread seems to be Walter either being stubborn or not getting it (or both).
February 12, 2019
On 2019-02-12 14:22, Nicholas Wilson wrote:

> Oh well, more things to discuss at dconf. That whole thread seems to be Walter either being stubborn or not getting it (or both).

Yes, exactly. Why can we overload some operators but not all? Even if a language doesn't support operator overloading it's possible to abuse:

int add(int a, int b)
{
    return a - b;
}

-- 
/Jacob Carlborg
February 12, 2019
On Tuesday, 12 February 2019 at 13:22:12 UTC, Nicholas Wilson wrote:
> Oh well, more things to discuss at dconf. That whole thread seems to be Walter either being stubborn or not getting it (or both).

Or your proposal has drawbacks you're underestimating.

This community really needs to stop defaulting to "We could totally use my awesome feature if Walter wasn't so stubborn" whenever discussing language changes.
February 13, 2019
On Tuesday, 12 February 2019 at 23:24:54 UTC, Olivier FAURE wrote:
> On Tuesday, 12 February 2019 at 13:22:12 UTC, Nicholas Wilson wrote:
>> Oh well, more things to discuss at dconf. That whole thread seems to be Walter either being stubborn or not getting it (or both).
>
> Or your proposal has drawbacks you're underestimating.

... go on. I don't see any drawbacks: its backwards compatible, intuitive (behaves like opBinary), useful. We should be making design decisions based on merit, not dismissing them not potential for abuse.

> This community really needs to stop defaulting to "We could totally use my awesome feature if Walter wasn't so stubborn" whenever discussing language changes.

Its because he always seems to do that:
    1) extern(C++, "strings")
    2) extern-std=c++nn
    3) dip1000(!) because the documentation is frankly not up to scratch.
    4) extern(C++) T[]
    5) dip1015
    ...

wherein
    1) he finally capitulated
    2) a (not particularly satisfactory) compromise was reached
    3) I _finally_ got him to update the spec. The docs are still shit.
    4) He believed that people want a std::vector interface (fair enough), but has has not responded since when it was pointed out that the interface is compatible with std::vector under C++'s implicit conversions
    5) fully 100% of the community thought the outcome of that DIP is wrong and that his reasoning is flawed.

Its a real pain working with him when he doesn't like what you're doing, and frankly needs to change. Sometimes he has good insight, but more and more I feel that he acts more like a gatekeeper and less like a leader.




February 12, 2019
On Tuesday, February 12, 2019 5:09:19 PM MST Nicholas Wilson via Digitalmars-d wrote:
> On Tuesday, 12 February 2019 at 23:24:54 UTC, Olivier FAURE wrote:
> > On Tuesday, 12 February 2019 at 13:22:12 UTC, Nicholas Wilson
> >
> > wrote:
> >> Oh well, more things to discuss at dconf. That whole thread seems to be Walter either being stubborn or not getting it (or both).
> >
> > Or your proposal has drawbacks you're underestimating.
>
> ... go on. I don't see any drawbacks: its backwards compatible, intuitive (behaves like opBinary), useful. We should be making design decisions based on merit, not dismissing them not potential for abuse.

Except that what you're proposing is precisely so that you can more easily abuse certain operators when overloading them. IMHO, if an operator is being overloaded in a manner that doesn't fit what you get with the basic arithmetic types, then that's bad practice. I know that some folks like to do it (especially in C++), but that's one of the big reasons that some languages (like Java) chose to not have operator overloading at all. People kept using them to mean things that didn't match their meaning for the built-in types. Such operator overloading abuse results in code that's harder to understand and maintain, because the operators are not being used with their normal meaning. It also doesn't play well with generic code, because such code is going to assume the normal arithmetic meanings for those operators, and so having a type that uses them differently with such code will tend to result in incorrect behavior (or in some cases, the code won't even compile).

We can't completely eliminate operator overloading abuse (e.g. someone doing something dumb like making + mean -), but just because it's possible to abuse one operator doesn't mean that we should make easier to abuse another.

- Jonathan M Davis



February 12, 2019
On Tue, Feb 12, 2019 at 11:24:54PM +0000, Olivier FAURE via Digitalmars-d wrote:
> On Tuesday, 12 February 2019 at 13:22:12 UTC, Nicholas Wilson wrote:
> > Oh well, more things to discuss at dconf. That whole thread seems to be Walter either being stubborn or not getting it (or both).
> 
> Or your proposal has drawbacks you're underestimating.
> 
> This community really needs to stop defaulting to "We could totally use my awesome feature if Walter wasn't so stubborn" whenever discussing language changes.

Frankly, I think it's a very good thing that in D comparison operators are confined to opEquals/opCmp.  If anything, I would vote for enforcing opEquals to return bool and bool only.

The reason for this is readability and maintainability.  Symbols like <= or == should mean one and only one thing in the language, and should not be subject to random overloaded interpretations. Being built-in operators, they are used universally in the language, and any inconsistency in semantics hurts readability, comprehension, and maintainability, such as C++'s free-for-all operator overloading, where any piece of syntax can have wildly-divergent interpretations (even completely different parse trees) depending on what came before.  For example, recently I came up with a C++ monstrosity where the lines:

        fun<A, B>(a, b);
        gun<T, U>(a, b);

have wildly-different, completely unrelated *parse trees*, as an illustration of how unreadable C++ can become.

Yes, it's extremely flexible, yes it's extremely powerful and can express literally *whatever* you want it to express. It's also completely unreadable and unmaintainable, because the surface structure of the code text becomes completely detached from the actual underlying semantics. I don't even want to imagine what debugging such kind of code must be like.

Operator overloading should be reserved for objects that behave like arithmetical entities in some way.  And especially comparison operators should not have any other meaning than the standard meaning.  It should be illegal to make == and <= mean something completely unrelated to each other.

If you need to redefine comparison operators, what you want is really a DSL wherein you can define operators to mean whatever you want.  The usual invocation of such a DSL as a compile-time argument, say something like this:

	myDsl!'a <= b'

contains one often overlooked, but very important element: the identifier `myDsl`, that sets it apart from other uses of `<=`, and clearly identifies which interpretation should be ascribed to the symbols found in the template argument.

See, the thing is, when you see a random expression with arithmetical operators in it, the expected semantics is the computation of some kind of arithmetic objects producing an arithmetical result -- because that's what such expressions mean in general, in the language.  It's abusive to overload that to mean something else entirely -- because there is no warning sign to the reader of the code that something different is happening. When you see a line like:

        fun<A, B>(a, b);

and then somewhere else a line like:

        gun<T, U>(a, b);

the actual behaviour of the code should be similar enough that you can correctly guess the semantics.  It should not be that the first line instantiates and calls a template function, whereas the second is evaluated as an expression with a bunch of overloaded comma and comparison operators.

Similarly, it should not be the case that:

	auto x = a <= b;

evaluates a comparison expression and assigns a boolean value to x, whereas:

	auto y = p <= q;

creates an expression object capturing p and q, that needs to be called later before it yields a boolean value.

With a string DSL, that little identifier `myDsl` (or whatever identifier you choose for this purpose) serves as a cue to the reader of the code that something special is happening here.  For example:

	auto y = deferred!`p <= q`;

immediately tells the reader of the code that the <= is to be understood with a different meaning than the usual <= operator. Just as an expression like:

	auto dg = (p, q) => p <= q;

by virtue of its different syntax tells the reader that the expression `p <= q` isn't being evaluated here and now, as it otherwise would be.

The presence of such visual cues is good, and is the way things should be done.

It should not be that something that looks like an expression, evaluated here and now, should turn out to do something else. That kind of free-for-all, can-mean-literally-anything semantics makes code unreadable, unmaintainable, and a ripe breeding ground for bugs -- someone (i.e., yourself after 3 months) will inevitably forget (or not know) the special meaning of <= in this particular context and write wrong code for it.


P.S. And as a bonus, a string DSL gives you the freedom to employ operators not found among the built-in D operators, for example:

	auto result = eval!`(f∘g)(√x ± √y)`;

And if you feel the usual strings literals are too cumbersome to use for long expressions, there's always the under-used token strings to the rescue:

	auto result = eval!q{
		( (f ∘ g)(√(x + y) ± √(x - y)) ) / |x|·|y| +
		2.0 * ∫ f(x)·dx
	};

The `eval` tells the reader that something special is happening here, and also provides a name by which the reader can search for the definition of the template that processes this expression, and thereby learn what it means.

Without this little identifier `eval`, it would be anyone's guess as to what the code is trying to do. Throw in C++-style SFINAE and Koenig lookup, and what ought to be a 10-second source tree search for an identifier easily turns into a 6-hour hair-pulling session of trying to understand exactly which obscure rules the C++ compiler applied to resolve those operators to which symbol(s) defined in which obscure files buried deep in the source tree.


T

-- 
Frank disagreement binds closer than feigned agreement.
February 13, 2019
On Wednesday, 13 February 2019 at 00:24:48 UTC, Jonathan M Davis wrote:
> Except that what you're proposing is precisely so that you can more easily abuse

Use, not abuse.

> certain operators when overloading them. IMHO, if an operator is being overloaded in a manner that doesn't fit what you get with the basic arithmetic types, then that's bad practice.

Yes, thats called abuse.

> People kept using [C++ operators] to mean things that didn't match their meaning for the built-in types. Such operator overloading abuse results in code that's harder to understand and maintain, because the operators are not being used with their normal meaning.

That falls down to convention. I personally think << for I/O is silly, but the thing that makes it work is that it is consistently applied.

> It also doesn't play well with generic code, because such code is going to assume the normal arithmetic meanings for those operators, and so having a type that uses them differently with such code will tend to result in incorrect behavior (or in some cases, the code won't even compile).

Actually it plays excellently with generic code. If I have a symbolic execution engine and I use it with a generic function, what results is a symbolic function, for free! You are then free to do what ever you please with, e.g. turn it into a DB query, send it off to a graph execution engine, optimise it, evaluate it at a later time, ... .

> We can't completely eliminate operator overloading abuse (e.g. someone doing something dumb like making + mean -), but just because it's possible to abuse one operator doesn't mean that we should make easier to abuse another.

It doesn't mean we shouldn't either, it bears no correlation. What we want to prevent is abuse, not potential for abuse. We already have massive potential for abuse, goto, operator overloading, mixins, etc., we have almost no _actual_ abuse. D is designed for someone who is sensible and knows how to use discretion.

Yes this proposal increases the potential for abuse. How much of that potential will actually materialise? History suggests not much, and that should be reflected in the decision making.

February 13, 2019
On Tuesday, 12 February 2019 at 23:24:54 UTC, Olivier FAURE wrote:
> On Tuesday, 12 February 2019 at 13:22:12 UTC, Nicholas Wilson wrote:
>> Oh well, more things to discuss at dconf. That whole thread seems to be Walter either being stubborn or not getting it (or both).
>
> Or your proposal has drawbacks you're underestimating.
>
> This community really needs to stop defaulting to "We could totally use my awesome feature if Walter wasn't so stubborn" whenever discussing language changes.

What doesn't explain his view point, at all most of the time. He just says no or says nothing. When you don't say anything without an explanation of why, it'll only natural to call that stubborn. An explanation you can have a discussion against, someone saying "no" not really.
February 13, 2019
On Wednesday, 13 February 2019 at 00:56:48 UTC, H. S. Teoh wrote:
> On Tue, Feb 12, 2019 at 11:24:54PM +0000, Olivier FAURE via Digitalmars-d wrote:
>> On Tuesday, 12 February 2019 at 13:22:12 UTC, Nicholas Wilson wrote:
>> > Oh well, more things to discuss at dconf. That whole thread seems to be Walter either being stubborn or not getting it (or both).
>> 
>> Or your proposal has drawbacks you're underestimating.
>> 
>> This community really needs to stop defaulting to "We could totally use my awesome feature if Walter wasn't so stubborn" whenever discussing language changes.
>
> Frankly, I think it's a very good thing that in D comparison operators are confined to opEquals/opCmp.  If anything, I would vote for enforcing opEquals to return bool and bool only.
>
> The reason for this is readability and maintainability.  Symbols like <= or == should mean one and only one thing in the language, and should not be subject to random overloaded interpretations. Being built-in operators, they are used universally in the language, and any inconsistency in semantics hurts readability, comprehension, and maintainability, such as C++'s free-for-all operator overloading, where any piece of syntax can have wildly-divergent interpretations (even completely different parse trees) depending on what came before.  For example, recently I came up with a C++ monstrosity where the lines:
>
>         fun<A, B>(a, b);
>         gun<T, U>(a, b);
>
> have wildly-different, completely unrelated *parse trees*, as an illustration of how unreadable C++ can become.
>
> Yes, it's extremely flexible, yes it's extremely powerful and can express literally *whatever* you want it to express. It's also completely unreadable and unmaintainable, because the surface structure of the code text becomes completely detached from the actual underlying semantics. I don't even want to imagine what debugging such kind of code must be like.
>
> Operator overloading should be reserved for objects that behave like arithmetical entities in some way.  And especially comparison operators should not have any other meaning than the standard meaning.  It should be illegal to make == and <= mean something completely unrelated to each other.
>
> If you need to redefine comparison operators, what you want is really a DSL wherein you can define operators to mean whatever you want.  The usual invocation of such a DSL as a compile-time argument, say something like this:
>
> 	myDsl!'a <= b'
>
> contains one often overlooked, but very important element: the identifier `myDsl`, that sets it apart from other uses of `<=`, and clearly identifies which interpretation should be ascribed to the symbols found in the template argument.
>
> See, the thing is, when you see a random expression with arithmetical operators in it, the expected semantics is the computation of some kind of arithmetic objects producing an arithmetical result -- because that's what such expressions mean in general, in the language.  It's abusive to overload that to mean something else entirely -- because there is no warning sign to the reader of the code that something different is happening. When you see a line like:
>
>         fun<A, B>(a, b);
>
> and then somewhere else a line like:
>
>         gun<T, U>(a, b);
>
> the actual behaviour of the code should be similar enough that you can correctly guess the semantics.  It should not be that the first line instantiates and calls a template function, whereas the second is evaluated as an expression with a bunch of overloaded comma and comparison operators.
>
> Similarly, it should not be the case that:
>
> 	auto x = a <= b;
>
> evaluates a comparison expression and assigns a boolean value to x, whereas:
>
> 	auto y = p <= q;
>
> creates an expression object capturing p and q, that needs to be called later before it yields a boolean value.
>
> With a string DSL, that little identifier `myDsl` (or whatever identifier you choose for this purpose) serves as a cue to the reader of the code that something special is happening here.  For example:
>
> 	auto y = deferred!`p <= q`;
>
> immediately tells the reader of the code that the <= is to be understood with a different meaning than the usual <= operator. Just as an expression like:
>
> 	auto dg = (p, q) => p <= q;
>
> by virtue of its different syntax tells the reader that the expression `p <= q` isn't being evaluated here and now, as it otherwise would be.
>
> The presence of such visual cues is good, and is the way things should be done.
>
> It should not be that something that looks like an expression, evaluated here and now, should turn out to do something else. That kind of free-for-all, can-mean-literally-anything semantics makes code unreadable, unmaintainable, and a ripe breeding ground for bugs -- someone (i.e., yourself after 3 months) will inevitably forget (or not know) the special meaning of <= in this particular context and write wrong code for it.
>
>
> P.S. And as a bonus, a string DSL gives you the freedom to employ operators not found among the built-in D operators, for example:
>
> 	auto result = eval!`(f∘g)(√x ± √y)`;
>
> And if you feel the usual strings literals are too cumbersome to use for long expressions, there's always the under-used token strings to the rescue:
>
> 	auto result = eval!q{
> 		( (f ∘ g)(√(x + y) ± √(x - y)) ) / |x|·|y| +
> 		2.0 * ∫ f(x)·dx
> 	};
>
> The `eval` tells the reader that something special is happening here, and also provides a name by which the reader can search for the definition of the template that processes this expression, and thereby learn what it means.
>
> Without this little identifier `eval`, it would be anyone's guess as to what the code is trying to do. Throw in C++-style SFINAE and Koenig lookup, and what ought to be a 10-second source tree search for an identifier easily turns into a 6-hour hair-pulling session of trying to understand exactly which obscure rules the C++ compiler applied to resolve those operators to which symbol(s) defined in which obscure files buried deep in the source tree.
>
>
> T

Always hear that D is somehow better than C++ for operators but it isn't in quite a few places already.

    int a;
    auto c = (a) <= 0; // ok
    auto d = (a) => 0; // not ok

For some reason Walter thought in D if you overload the "+" operator you couldn't make it not commutative?? Still never got a reply to that, so I'll just assume he didn't know what commutative was. Yes you can make "a + b != b + a" be true quite easily.

Then you have things like "min" where you can do:

    foo( a /min/ b );

To get the "min" value between a and b. I guess you could use this as an example of why not to allow. But at the same time, we're already pretty much there. That includes "==" operator in that. So the comparisons operators aren't even consistent.



February 13, 2019
On Wednesday, 13 February 2019 at 00:56:48 UTC, H. S. Teoh wrote:
> Frankly, I think it's a very good thing that in D comparison operators are confined to opEquals/opCmp.

So do I. Many more objects have partial ordering than arithmetic, having opCmp under opBinary would be annoying.

> If anything, I would vote for enforcing opEquals to return bool and bool only.

That would be a backwards incompatible change, like it or not.

> The reason for this is readability and maintainability.  Symbols like <= or == should mean one and only one thing in the language, and should not be subject to random overloaded interpretations. Being built-in operators, they are used universally in the language, and any inconsistency in semantics hurts readability, comprehension, and maintainability, such as C++'s free-for-all operator overloading, where any piece of syntax can have wildly-divergent interpretations (even completely different parse trees) depending on what came before.  For example, recently I came up with a C++ monstrosity where the lines:
>
>         fun<A, B>(a, b);
>         gun<T, U>(a, b);
>
> have wildly-different, completely unrelated *parse trees*, as an illustration of how unreadable C++ can become.
>
> Yes, it's extremely flexible, yes it's extremely powerful and can express literally *whatever* you want it to express. It's also completely unreadable and unmaintainable, because the surface structure of the code text becomes completely detached from the actual underlying semantics. I don't even want to imagine what debugging such kind of code must be like.

Thank goodness we use ! for template and don't have `,` available for overloading!

> Operator overloading should be reserved for objects that behave like arithmetical entities in some way.

Like symbolic math.

> And especially comparison operators should not have any other meaning than the standard meaning.  It should be illegal to make == and <= mean something completely unrelated to each other.

They do already mean something completely different, <= is an ordering, == is equality. Yes it would be bad for (a <= b) == (a == b) to be false. I'm sure you could already achieve that outcome, would you though? Of course not, it'd be stupid.

> If you need to redefine comparison operators, what you want is really a DSL wherein you can define operators to mean whatever you want.

Yes.

> The usual invocation of such a DSL as a compile-time argument, say something like this:
>
> 	myDsl!'a <= b'
>
> contains one often overlooked, but very important element: the identifier `myDsl`, that sets it apart from other uses of `<=`, and clearly identifies which interpretation should be ascribed to the symbols found in the template argument.

Its also much uglier and does not commute with things that use <= i.e. generic functions.

> See, the thing is, when you see a random expression with arithmetical operators in it, the expected semantics is the computation of some kind of arithmetic objects producing an arithmetical result -- because that's what such expressions mean in general, in the language.  It's abusive to overload that to mean something else entirely -- because there is no warning sign to the reader of the code that something different is happening.

Yes thats the use/abuse distinction, see my other post.

When you see a line like:
>
>         fun<A, B>(a, b);
>
> and then somewhere else a line like:
>
>         gun<T, U>(a, b);
>
> the actual behaviour of the code should be similar enough that you can correctly guess the semantics.  It should not be that the first line instantiates and calls a template function, whereas the second is evaluated as an expression with a bunch of overloaded comma and comparison operators.

Indeed! That is not what is being proposed at all!

> Similarly, it should not be the case that:
>
> 	auto x = a <= b;
>
> evaluates a comparison expression and assigns a boolean value to x, whereas:
>
> 	auto y = p <= q;
>
> creates an expression object capturing p and q, that needs to be called later before it yields a boolean value.

No. auto y = p <= q; should not e.g. open a socket (you could probably do that already with an impure opCmp). Being able to capture the expression `p <= q` is the _entire point_ of the proposal.

> With a string DSL, that little identifier `myDsl` (or whatever identifier you choose for this purpose) serves as a cue to the reader of the code that something special is happening here.  For example:
>
> 	auto y = deferred!`p <= q`;
>
> immediately tells the reader of the code that the <= is to be understood with a different meaning than the usual <= operator.

Its also much uglier and does not commute with things that use <= i.e. generic functions.

> Just as an expression like:
>
> 	auto dg = (p, q) => p <= q;
>
> by virtue of its different syntax tells the reader that the expression `p <= q` isn't being evaluated here and now, as it otherwise would be.

Can't do symbolic computation with that.

> The presence of such visual cues is good, and is the way things should be done.
>
> It should not be that something that looks like an expression, evaluated here and now, should turn out to do something else. That kind of free-for-all, can-mean-literally-anything semantics makes code unreadable, unmaintainable, and a ripe breeding ground for bugs -- someone (i.e., yourself after 3 months) will inevitably forget (or not know) the special meaning of <= in this particular context and write wrong code for it.

Type autocompletion will tell you the result of  p <= q; which at that point if it is still unclear you have bigger problems. In generic code you have no choice but to assume that p <= q; is a comparison, if someone is using that with a symbolic engine then the meaning doesn't change.

> P.S. And as a bonus, a string DSL gives you the freedom to employ operators not found among the built-in D operators, for example:
>
> 	auto result = eval!`(f∘g)(√x ± √y)`;
>
> And if you feel the usual strings literals are too cumbersome to use for long expressions, there's always the under-used token strings to the rescue:
>
> 	auto result = eval!q{
> 		( (f ∘ g)(√(x + y) ± √(x - y)) ) / |x|·|y| +
> 		2.0 * ∫ f(x)·dx
> 	};
>
> The `eval` tells the reader that something special is happening here, and also provides a name by which the reader can search for the definition of the template that processes this expression, and thereby learn what it means.
>
> Without this little identifier `eval`, it would be anyone's guess as to what the code is trying to do.

I would expect it the compute the tuple
(f(g(sqrt(x+y)) + sqrt(x-y)/(abs(x).dot(abs(y)) + 2*integrate(f),
(f(g(sqrt(x+y)) - sqrt(x+y)/(abs(x).dot(abs(y)) + 2*integrate(f)

(you missed the bounds on the integral and x is ambiguous in the integral)

What else would I think it would do? If that guess is wrong then the person has abused the operators, if its correct that thats a win. I'd bet money you could do just that in Julia. I'm not suggesting we go that far but they would definitely consider that a feature.

> Throw in C++-style SFINAE and Koenig lookup, and what ought to be a 10-second source tree search for an identifier easily turns into a 6-hour hair-pulling session of trying to understand exactly which obscure rules the C++ compiler applied to resolve those operators to which symbol(s) defined in which obscure files buried deep in the source tree.

Its a good thing we don't have SFINAE and Koenig lookup.