templating opEquals/opCmp (e.g. for DSL/expression templates)

Rubn where at is.this
Wed Feb 13 01:24:45 UTC 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.





More information about the Digitalmars-d mailing list