September 28, 2016
On 28.09.2016 06:02, Walter Bright wrote:
> On 9/27/2016 6:18 PM, Minty Fresh wrote:
>> So, I'm just interested in other people's thoughts are on the subject,
>> and
>> whether there are any plans to allow overloading these operators
>> separately.
>
> 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.
> ...

The restrictions enforce that <= computes a boolean result. "less than or equal" is not necessarily the same as "decide /now/ whether this is less than or equal and give me a boolean". The restriction is annoying if you are implementing e.g. symbolic algebra. opCmp is a good thing to have for the common case, but why disable opBinary?


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

One anecdote: In the big-O library discussion, my initial proposal relied on string parsing. Andrei promptly criticized this and used operator overloading instead.

> An excellent example of that is the std.regex package.
> ...

It's actually a bad example, because it is irrelevant: it is obviously a bad idea to implement regex using operator overloading, because the regex operators have no D equivalent.

Assume I have two symbolic expressions a and b:

Expression a=variable("a"), b=variable("b");

Why should I be allowed to do

auto c = a + b; // symbolic value a + b

but not

auto d = a <= b; // symbolic value a <= b

The string parsing approach is not useful here: how do I use existing expressions that are accessible in the scope to build new expressions?
September 28, 2016
On Wednesday, 28 September 2016 at 13:58:35 UTC, Timon Gehr wrote:
> Assume I have two symbolic expressions a and b:
>
> Expression a=variable("a"), b=variable("b");
>
> Why should I be allowed to do
>
> auto c = a + b; // symbolic value a + b
>
> but not
>
> auto d = a <= b; // symbolic value a <= b
>
> The string parsing approach is not useful here: how do I use existing expressions that are accessible in the scope to build new expressions?

Thanks for expressing this so clearly. This is exactly the sort of thing I want to be able to do.
September 28, 2016
On Wed, 28 Sep 2016 10:06:43 +0000, pineapple wrote:
> I think the cleanest possible solution would be to allow comparison operators to be overridden with opBinary and opBinaryRight, as well, but use opCmp and opEquals instead if they exist. I can't think of any way this would break existing code.

What happens with this?

  struct MyInt {
    int value;
    MyInt opBinary(string op)(MyInt other) {
      return MyInt(mixin("value " ~ op ~ " other.value"));
    }
  }

Obvious pattern for wrapping a value. Comparisons used to be unsupported, producing an appropriate error message:

  Error: need member function opCmp() for struct MyInt to compare

Now they produce a different, opaque error message, which will have to be created, but the current equivalent is:

  Error: recursive opCmp expansion

That's not a breakage, but it is an unfortunate direction for the quality of error messages.
September 28, 2016
On Tue, 27 Sep 2016 23:05:54 -0700, Jonathan M Davis via Digitalmars-d wrote:

> On Wednesday, September 28, 2016 03:28:50 Minty Fresh via Digitalmars-d wrote:
>> Lastly, if operators are intended to behave so strictly, why does this not extend then to binary arithmetic operators (+, -, *, /, %, etc.) They don't follow the same rules as the binary relational operators, after all.
> 
> It's not possible in the general case to define the arithmetic operators as functions of each other. They actually need to be defined separately in order to work.

Specifically for making efficient programs. Increment, decrement, and
test for equality is sufficient mathematically, but we want programs that
terminate in our lifetimes.
</pedantry>
September 28, 2016
On Wednesday, 28 September 2016 at 14:27:58 UTC, Chris Wright wrote:
> Increment, decrement, and
> test for equality is sufficient mathematically, but we want programs that
> terminate in our lifetimes.
> </pedantry>

Only for BigInts you need those three.  For ints decrement is not needed.  And for most other data types increment/decrement is not sufficient, e.g. for reals or any kind of vectors/matrices.
September 28, 2016
On 9/28/2016 2:48 AM, Matthias Bentrup wrote:
> In Mathematics the comparison operators are also commonly used for semi orders,
> which cannot be implemented by opCmp, because opCmp has no way to indicate that
> two values are incomparable.
>
> Interestingly the floating point types are semi ordered (due to NaNs), and for
> those D has some (non-overridable and deprecated) operators like !<, which would
> be easily extendable to any semi order, whereas the suggested replacement (i.e.
> test for NaNs manually) works only on floats.

I'm aware of the limitation, and a fair amount of thought was given on it, but the solution used generally for unordered is to test for it separately, and people seem happy with it.

The !<, etc., operators have been an utter failure (not a failure technically, but people found them hopelessly confusing and would not use them).

September 28, 2016
On Wednesday, 28 September 2016 at 04:02:59 UTC, Walter Bright wrote:
> 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.

I don't really come from a C++ background. Actually, I have a strong distaste for the language. Professionally, I word with Ruby.

Again, string DSLs face a number of issues. A few that come to mind are:
  1. A parser + code generator must be implemented, which is a lot of additional work.
  2. The quality and precision of error messages degrades.
  3. You loose the ability to perform actions like making declarations within the context of the DSL (ie. assigning a commonly re-used expression to a variable), unless your DSL is turing complete.

> An excellent example of that is the std.regex package.

You know, if someone had managed to implement a regex DSL using operator overloading, something must have gone _terribly_ wrong. Regex is traditionally done in strings, lest the language supports regex literals.
September 28, 2016
On 9/28/2016 6:58 AM, Timon Gehr wrote:
>> An excellent example of that is the std.regex package.
> It's actually a bad example, because it is irrelevant: it is obviously a bad
> idea to implement regex using operator overloading, because the regex operators
> have no D equivalent.

Yet this "obviously bad" idea regularly resurfaces as a cool use of expression templates and respected people in the industry like it and advocate it.


> Assume I have two symbolic expressions a and b:
>
> Expression a=variable("a"), b=variable("b");
>
> Why should I be allowed to do
>
> auto c = a + b; // symbolic value a + b
>
> but not
>
> auto d = a <= b; // symbolic value a <= b

Because there is no way to stop the former but still have operator overloading.


> The string parsing approach is not useful here: how do I use existing
> expressions that are accessible in the scope to build new expressions?

You seem to be asking for expression templates. They do actually work in D, but I recommend against them.

To reiterate, operator overloading exists in D to support the inclusion of arithmetic library types. Any other purpose is discouraged, and that includes expression templates and things like << for iostreams.

AST macros are an alternative to expression templates. Having AST macros has been proposed before multiple times, and has been debated at length. I haven't seen any new information that would justify reopening the issue.
September 28, 2016
On Wednesday, 28 September 2016 at 20:16:08 UTC, Walter Bright wrote:
> Because there is no way to stop the former but still have operator overloading.
>
> To reiterate, operator overloading exists in D to support the inclusion of arithmetic library types. Any other purpose is discouraged, and that includes expression templates and things like << for iostreams.

This is an obsolete way of thinking about operators. Operator overloading exists to make the programmer's life easier, and limiting the ways in which a valuable tool can be used drives people to languages which allow them to be more expressive.

For my job I write Python. We use a database layer called sqlalchemy that allows us to express queries by writing, for example, `session.query(some_table).filter(row1 == row2)` and I cannot begin to express how useful this is when writing code, when maintaining code, and when attempting to understand code that someone else wrote.

The objective should never be to stop the programmer from doing something because you personally dislike the practice.

September 28, 2016
On Wednesday, 28 September 2016 at 20:16:08 UTC, Walter Bright wrote:
> To reiterate, operator overloading exists in D to support the inclusion of arithmetic library types. Any other purpose is discouraged, and that includes expression templates and things like << for iostreams.

It seeds rather harsh to discourage every possible use case of a feature sans one, simply because one does not agree with the others.