February 13, 2019
On Wednesday, 13 February 2019 at 01:24:45 UTC, Rubn wrote:
> 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

Oooh. That one's nasty! Technically unambiguous, but nasty!

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

With opBinary doing something completely different to opBinaryRight? Anyway that falls under the category of deliberate abuse, something we should not be considering, if people do that kind of thing then they should get what they deserve.

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

Indeed.
February 12, 2019
On Wed, Feb 13, 2019 at 01:24:45AM +0000, Rubn via Digitalmars-d wrote: [...]
> 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

Haha... yeah, I *thought* the choice of => for lambda syntax was not a good idea.  But people seemed to love it at the time, what can I say? :-/


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

You don't need operator overloading for that; '+' is already non-commutative with IEEE 754 floating-point. In fact, most of IEEE 754 arithmetic is non-commutative, even though it appears to be in most cases.


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

Yes, that's a fine example of operator overloading abuse.


> But at the same time, we're already pretty much there. That includes "==" operator in that. So the comparisons operators aren't even consistent.
[...]

Don't get me wrong, the situation in D isn't perfect, but imperfection shouldn't be a reason to open the door to worse things.


T

-- 
If you look at a thing nine hundred and ninety-nine times, you are perfectly safe; if you look at it the thousandth time, you are in frightful danger of seeing it for the first time. -- G. K. Chesterton
February 13, 2019
On Wednesday, 13 February 2019 at 02:37:50 UTC, H. S. Teoh wrote:
> On Wed, Feb 13, 2019 at 01:24:45AM +0000, Rubn via Digitalmars-d wrote: [...]
>> 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.
>
> Yes, that's a fine example of operator overloading abuse.
>
>
>> But at the same time, we're already pretty much there. That includes "==" operator in that. So the comparisons operators aren't even consistent.
> [...]
>
> Don't get me wrong, the situation in D isn't perfect, but imperfection shouldn't be a reason to open the door to worse things.

Imperfection should be a reason for adding things to make it better. That they can be abused and nobody does because that would be a stupid thing to do is of no relevance and certainly not a reason to not do it.


February 13, 2019
On Wednesday, 13 February 2019 at 02:37:50 UTC, H. S. Teoh wrote:
> [snip}
>
> Haha... yeah, I *thought* the choice of => for lambda syntax was not a good idea.  But people seemed to love it at the time, what can I say? :-/
>

What do you prefer instead? Can C++'s -> cause similar ambiguities?
February 13, 2019
On Wednesday, 13 February 2019 at 02:49:21 UTC, Nicholas Wilson wrote:
> [snip]
>
> Imperfection should be a reason for adding things to make it better. That they can be abused and nobody does because that would be a stupid thing to do is of no relevance and certainly not a reason to not do it.

This kind of gets in to the opt-in vs. opt-out argument. If there were a attribute that was something like @enable("extended-operator-overloading") that would allow more operators to be overloaded only within those blocks, then it allows the default to be something restricted but gives users the option to make something different if needed.
February 13, 2019
On Wed, Feb 13, 2019 at 12:12:17PM +0000, jmh530 via Digitalmars-d wrote:
> On Wednesday, 13 February 2019 at 02:37:50 UTC, H. S. Teoh wrote:
> > [snip}
> > 
> > Haha... yeah, I *thought* the choice of => for lambda syntax was not a good idea.  But people seemed to love it at the time, what can I say?  :-/
> > 
> 
> What do you prefer instead? Can C++'s -> cause similar ambiguities?

I'll admit, I've never really thought too hard about it.  But the main idea is to choose something that can't be confused for something else. Since -> isn't a token in D, it seems to be a viable candidate. But it might be confusing for the, *ahem*, droves of C++ coders who just can't wait to migrate to D. ;-)


T

-- 
In a world without fences, who needs Windows and Gates? -- Christian Surchi
February 13, 2019
On Tuesday, 12 February 2019 at 04:15:34 UTC, Nicholas Wilson wrote:
> With opBinary and opUnary it is possible to create types that span the binary and unary operators. However opEquals can only be used to types that compare for equality not inequality, since a == b -> a.opEquals(b) can return whatever type it likes but a == b -> !(a.opEquals(b)) returns a bool. opCmp can't be used at all since a <=> b -> a.opCmp(b) <=> 0 which evaluates to a bool.
>
> I propose that in addition to the current (non-template) forms of opCmp/opEquals
>
> struct A { bool opEquals(A rhs); int opCmp(A rhs); }
>
> we allow
>
> struct B
> {
>     T opEquals(string op)(B rhs); // op is "<" "<=" etc.
>     T opCmp(string op)(B rhs);    // op is "==" "!="
> }
>
> where T is any arbitrary type.
>
> see also https://github.com/k3kaimu/dranges/blob/master/source/dranges/_lambda.d

Any argument about this should consider what was necessary to make std.typecons.Proxy work properly for floating point.

https://github.com/dlang/phobos/pull/3927

and the original forum discussion

https://forum.dlang.org/post/vfgawgvzzfgjhshavmlq@forum.dlang.org
February 13, 2019
On Wed, Feb 13, 2019 at 01:50:21AM +0000, Nicholas Wilson via Digitalmars-d wrote:
> 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.

Actually, contrary to what Andrei has claimed in the past, opCmp (as currently implemented) does NOT allow for a consistent definition of a partial ordering.  I know because I've tried to do it with an integer set type.  The problem is that when opCmp returns 0, it's ambiguous whether it means "incomparable" or "equal".  This makes it impossible to make <= equivalent to the is-subset predicate. For example, here's a prospective implementation:

	struct IntSet {
		Impl impl;
		int opCmp(in IntSet b) {
			bool isSubset = impl.isSubsetOf(b.impl);
			bool isSuperset = b.impl.isSubsetOf(impl);

			if (isSubset && isSuperset)
				return 0;
			else if (isSubset)
				return -1;
			else if (isSuperset)
				return 1;
			else
				return 0; // incomparable
		}
		bool opEquals(in IntSet b) {
			return impl.isSubsetOf(b.impl) &&
				b.impl.isSubsetOf(impl);
		}
	}

Efficiency concerns aside (should not need to compute two-way subset relation just to determine <=, for example), this looks good, right?

Nope.  Suppose s is a subset of t.  Then opCmp would return -1, and the predicate s <= t would be true, because s <= t lowers to:

	s.opCmp(t) <= 0

Now suppose s and t are disjoint (i.e., incomparable). According to the spec, opCmp should return 0 in this case.  But then:

	assert(!(s <= t));

fails, because opCmp returns 0.  So we cannot distinguish between s being a subset of t vs. s and t being incomparable by using the <= operator.  Similarly for >=.  Therefore, s <= t cannot represent the is-subset operation.

We can try to redefine opCmp to return something different from what's outlined above, but we'd run into other problems.

tl;dr: opCmp does NOT support partial orderings in any real sense (you have to accept that <= and >= are true for incomparable elements!). Only linear orderings work correctly in all cases.


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

I know.  That's why I haven't proposed it yet. :-D


> > 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.
[...]
> Thank goodness we use ! for template and don't have `,` available for overloading!

Yeah, I was very happy when we finally deprecated the evil comma operator. Good riddance.


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

I know what you're trying to drive at, but there's a difference arithmetic in code vs. arithmetic in math. An expression in code is generally assumed to execute when control flow reaches that line of code. It's unexpected behaviour for what looks like an assignment of the result of an expression to instead assign the expression itself as something to be evaluated later.


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

What I meant was that == and <= should behave consistently with each other.  Ideally both <= and == should be handled by the same function. Then consistency would be guaranteed. But for practical reasons we separate them, one reason being that often equality is much cheaper to compute than linear ordering.


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

Ugliness is debatable; I find that the identifier serves as a good marker to indicate departure from usual semantics.

As for not commuting with generic functions, do you have a specific example in mind?  Because all the obvious (to me) examples I can think of are cases where I think would be better off *not* working with generic code, because the generic code expects one set of semantics but overloading opCmp/opEquals to return arbitrary objects produce a different set of semantics.


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

And that's the point I disagree on.  The current expectation of a line of code like `auto y = p <= q;` is that typeof(y) == bool.  For it to return a type other than bool is unexpected behaviour, and leads to subtle differences in semantics that will likely result in bugs.

Explicitly marking it, e.g., as `auto y = symbolic!"p <= q";` makes it much more obvious what's really going on with the code.  Yes it's ugly, but at the same time this ugliness is IMO necessary warning for the would-be code maintainer to understand that the semantics depart from the usual expectations.


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

My point was that there are no such operators as ∘ or ± in D, so even if you had free-for-all operator overloading you couldn't implement such an expression.  Using a DSL frees you from that constraint.

And because there are no such operators in D, they have no standard meaning defined by the language, so different authors likely have different definitions for them. So even in the case where you *could* add arbitrary operators to the language, you'll end up with a mess as soon as your code imports two libraries that happen to overload some of the same custom operators -- it would be hard to tell which set of semantics apply to the operators in any given piece of code; you'd have to study the context to be sure.  Having the `eval` or whatever else prefixed identifier in front of the DSL resolves that question instantly.


T

-- 
In a world without fences, who needs Windows and Gates? -- Christian Surchi
February 13, 2019
On Wed, Feb 13, 2019 at 01:24:45AM +0000, Rubn via Digitalmars-d wrote: [...]
>     int a;
>     auto c = (a) <= 0; // ok
>     auto d = (a) => 0; // not ok
[...]

Argh, I just realized that this example is invalid: the greater-than-or-equal operator is >=, not =>.

There is no ambiguity.

mouth.open
     .insert(foot);


T

-- 
Tech-savvy: euphemism for nerdy.
February 13, 2019
On Wednesday, 13 February 2019 at 18:42:43 UTC, H. S. Teoh wrote:
> On Wed, Feb 13, 2019 at 01:50:21AM +0000, Nicholas Wilson via Digitalmars-d wrote:
>> 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.
>
> Actually, contrary to what Andrei has claimed in the past, opCmp (as currently implemented) does NOT allow for a consistent definition of a partial ordering.  I know because I've tried to do it with an integer set type.  The problem is that when opCmp returns 0, it's ambiguous whether it means "incomparable" or "equal".  This makes it impossible to make <= equivalent to the is-subset predicate. For example, here's a prospective implementation:
>
> 	struct IntSet {
> 		Impl impl;
> 		int opCmp(in IntSet b) {
> 			bool isSubset = impl.isSubsetOf(b.impl);
> 			bool isSuperset = b.impl.isSubsetOf(impl);
>
> 			if (isSubset && isSuperset)
> 				return 0;
> 			else if (isSubset)
> 				return -1;
> 			else if (isSuperset)
> 				return 1;
> 			else
> 				return 0; // incomparable
> 		}

opCmp is allowed to return float, making it possible to distinguish all 4 possible values
float opCmp(in IntSet b) {
   bool isSubset = impl.isSubsetOf(b.impl);
   bool isSuperset = b.impl.isSubsetOf(impl);

   if (isSubset && isSuperset)
      return 0;
   else if (isSubset)
      return -1;
   else if (isSuperset)
      return 1;
   else
      return float.init; // incomparable - NaN
}

This works fine, I've used it in several types in my libraries.