min/max `<?` / `>?` expressions
This bugs me every few weeks for decades... I still want this just as much as the first say it ever came up for me:
It's really annoying to import a module for min/max, it feels so 'heavy' for such a trivial operation. It's also annoying that they're functions; you have to step-in/step-out etc while debugging code, and they are so frequently used in hot-loops, while calling functions in debug/unoptimised code just slows it down needlessly, even more than unoptimised code already it...
C++ has a non-standard language extension, and I think it's also present in C# that adds a min/max operator. It is this:
a <? b and a >? b, which simply expands a < b ? a : b and a > b ? a : b.
I desperately want these in D... they're just so nice and readable, don't require bulky imports, efficient in debug builds, nicer when working in the debugger, etc...
I know these are all minor nuisances, but the practical result is that I very rarely actually import min/max, and almost always just write the expression out a < b ? a : b, which damages code readability, particularly when a or b are long-ish expressions.
This could be implemented with trivial lowering. It's lame when other languages have nice QOL features that we don't have in D, especially like this which are just cosmetic and pose no threats.
#20626 ...just trying it on!
Maybe it's because I don't worship at the altar of C++, but I don't see how these messes of random punctuation are more readable than the status quo.
Suggesting that x+y*z < object.method(x) ? x+y*z : object.method(x) is a shit thing to read is not worshipping the alter of C++... and it's not even in C++!
I feel like you didn't actually read the text above...
Suggesting that
x+y*z < object.method(x) ? x+y*z : object.method(x)is a shit thing to read is not worshipping the alter of C++... and it's not even in C++! I feel like you didn't actually read the text above...
I feel like you didn't read my post, either. x+y*z <? object.method(x) is ugly. Why the question mark? Is it supposed to be meaningful or was it just a symbol that happened to be available? We should copy the syntax verbatim from a C++ extension because... why, exactly?
The question mark because it's short-hand for the ?: operator which eliminates the repetition:
x < y ? x : y => x <? y
You don't need to repeat x and y twice...
If you can't see how the question mark is obviously logical... 🤷
x.min(y) or min(x, y) Is really the alternative. Yes, you have to import std.algorithm. But I find it very readable.
If we had more ?-based punctuation, it might make sense to tack on, but we don't yet.
Andrei originally also proposed the "elvis operator" ?:, as in x ?: y, as a substitute for x ? x : y which depends on the truthiness of x.
Suggesting that
x+y*z < object.method(x) ? x+y*z : object.method(x)is a shit thing to read is not worshipping the alter of C++... and it's not even in C++! I feel like you didn't actually read the text above...I feel like you didn't read my post, either.
x+y*z <? object.method(x)is ugly. Why the question mark? Is it supposed to be meaningful or was it just a symbol that happened to be available?
It's meaningful, it's logical, and I just explained it twice.
We should copy the syntax verbatim from a C++ extension because... why, exactly?
It's not C++. This isn't in C++. I already said that too.
Suggesting that
x+y*z < object.method(x) ? x+y*z : object.method(x)is a shit thing to read is not worshipping the alter of C++... and it's not even in C++! I feel like you didn't actually read the text above...I feel like you didn't read my post, either.
x+y*z <? object.method(x)is ugly. Why the question mark? Is it supposed to be meaningful or was it just a symbol that happened to be available?It's meaningful, it's logical, and I just explained it twice.
We should copy the syntax verbatim from a C++ extension because... why, exactly?
It's not C++. This isn't in C++. I already said that too.
Don't complain to me about your choice to reply to the same post twice with the same content.
🤨
Now I'm just confused...
Thanks for posting the suggestion.
I want to underscore an important point. a>b?a:b evaluates a twice sometimes, and b the other times. This is an irksome quality of ?:. Any solution should not have this double evaluation.
Introducing <? and >? as operators seems like a big ask. I use min/max now and then, but not that often. Is it really used so much that it justifies new operators? How far should we go with that? Also, I know implicitly what min/max do. I don't have to remember is it <? or ?<, or what the operator precedence is. It adds more complexity to the language and the specification and the work someone has to go to use it.
I agree that importing std.algorithm.comparison can be a bit heavyweight. That module imports the usual ghastly panopoly of other modules:
import std.functional : unaryFun, binaryFun, lessThan, greaterThan;
import std.range.primitives;
import std.traits;
import std.meta : allSatisfy, anySatisfy;
import std.typecons : tuple, Tuple, Flag, Yes;
import std.internal.attributes : betterC;
But all is not lost. It turns out that the definition of max is trivial:
T max(T, U)(T a, U b)
if (is(T == U) && is(typeof(a < b)))
{
return a < b ? b : a;
}
I was pleasantly surprised to see that! It's a thing of beauty, ain't it? In fact, it will still work as:
T max(T, U)(T a, U b) => a < b ? b : a;
Who wouldn't be proud to take that to the Prom? As a bonus, it only evaluates each argument once.
As for debugging issues, if it's inlined there's no further issue with that. The same goes for using pragma(inline, true) for non-optimized builds.
Therefore, unless I made an egregious error in this analysis, I suggest just adding that line to your code.
I would much rather have min and max as keywords and binary operators, but that’s probably not possible. The next best thing is to alias min and max in object.d, so you don’t have to import something explicitly.
Why are they in algorithm and not math?
For lvalues a and b, is a <? b an lvalue or an rvalue? If yes, and a and b are equal, what is the result referencing?
Also, I don’t get why <?; IMO, if anything, it should be ?<. It’s in a similar spirit as ?: (which is strictly worse than ?? because ?? is typed faster).
a ?< b could lower to (function auto ref (auto ref lhs, auto ref rhs) => lhs < rhs ? lhs : rhs)(a, b), and similar for operators <=, >, and >=, too. Essentially, ?op means: evaluate lhs op rhs and if true, return lhs, otherwise return rhs.
Thinking about it:
&is a bit-wise minimum,&&is a boolean minimum, why not add&&&for the value minimum?|is a bit-wise maximum,||is a boolean maximum, why not add|||for the value maximum?
C++ has a non-standard language extension, and I think it's also present in C# that adds a min/max operator.
It’s hard to prove a negative, but I haven’t found any evidence GCC or Clang or MSVC support these. C# does not have min/max operators according to this list of operators, also I follow C#’s development, proposals, etc., and I haven’t seen these.
but I haven’t found any evidence GCC or Clang or MSVC support these
https://gcc.gnu.org/onlinedocs/gcc-4.3.1/gcc/Deprecated-Features.html
The G++ minimum and maximum operators (
<?and>?) and their compound forms (<?=) and>?=) have been deprecated and are now removed from G++. Code using these operators should be modified to usestd::minandstd::maxinstead.
https://gcc.gnu.org/onlinedocs/gcc-4.3.1/gcc/Deprecated-Features.html
The G++ minimum and maximum operators (
<?and>?) and their compound forms (<?=) and>?=) have been deprecated and are now removed from G++. Code using these operators should be modified to usestd::minandstd::maxinstead.
That’s strong evidence that those operators aren’t a good idea. The G++ devs probably don’t deprecate and remove features for no good reason.
That’s strong evidence that those operators aren’t a good idea. The G++ devs probably don’t deprecate and remove features for no good reason.
The evidence shows that it wasn't adopted, not that it wasn't necessarily a good idea. Pretty easy to understand why it was removed from g++; if it didn't move the standard then there's no surprise they would remove it and comply with the standard.
The advantage is that there are 2 operands and it's not a grammatical mess; where a < b ? a : b has 4 operands, and implies the double-evaluation of each one. Favouring an intrinsic is a bland choice; but even then, they need to be reliably implemented, and they're not; They tend to be left to the optimiser to work it out!
Personally, I would say that I find min/max far more common than divide; nobody with programming experience uses divide if they can avoid it because it feels like a crime; you tend to multiply by the reciprocal if anything. I've never worked with a colleague who would write x / 16 or x % 16; people write x >> 4/x & f, even though it's the same thing; writing / causes visceral distress to non-web programmers.
min and max are single-cycle opcodes, they only accept 2 operands - not 4, they are operations that belong alongside + - | &, etc; they are lower-level than * and WAAAAY faster than % /, yet we make them a function and mod/div get operators? min/max are much more frequent than mod and divide in my experience.
Anyway, whatever.
Imagine if - was an optimisation of the expression a + ~b + 1... we should remove - and add sub as a macro for the optimiser to catch ;) ... that's basically how I see this; can't access a fundamental mathematical operation, hoping the optimiser gets it, and what about non-optimised builds?
That’s strong evidence that those operators aren’t a good idea. The G++ devs probably don’t deprecate and remove features for no good reason.
The evidence shows that it wasn't adopted, not that it wasn't necessarily a good idea.
What do you mean by adopted? I mean by users, not the C++ standard committee. G++ has a lot of extensions that have a low probability of becoming standard C++, yet they’re kept in and maintained because they provide value to some users and removing them would break these people’s code. The G++ devs couldn’t have removed the four operators (min/max, min/max-assign) if they had significant adoption, which proves that they didn’t.
I don’t know what’s so bad about having min and max be functions. You don’t have to explain to anyone what max(a, b) means. I give you that the assign operator isn’t easily expressed without duplication such as a = max(a, b), which is only ever an issue when a is a long expression or if a has side-effects. For those cases, we could provide max_assign(T, U)(ref T, U) which works really nice with D’s UFCS: a.max_assign(b). The implementation of max_assign is so trivial, that it’s unlikely to be added to Phobos.
Any compiler that you’d reasonably use optimizes the pattern of min/max properly.
minandmaxare single-cycle opcodes, they only accept 2 operands - not 4, they are operations that belong alongside+-|&, etc; they are lower-level than*and WAAAAY faster than%/, yet we make them a function andmod/divget operators?min/maxare much more frequent than mod and divide in my experience.
That’s not the metric by which it was decided if something became an operator. The operators generally follow common mathematics. If every school child knows it, programming languages make it an operator, generally speaking; and obviously, the reverse isn’t true. We don’t have bit rotation operators either despite those operations being 1–2 cycles; bit rotations are even slightly harder to implement than min and max. Not having division with remainder as an operator despite it being an operation that’s taught in elementary school (4th grade where I live) would be surprising and annoying.
Coming from mathematics, the obvious choice is:
&is a bit-wise minimum,&&is a boolean minimum, and&&&is value minimum.|is a bit-wise maximum,||is a boolean maximum, and|||is value maximum.
I would never in my life conflate &&& with |||, but I can’t say that about <? and >?.