chapel-lang / chapel

a Productive Parallel Programming Language
https://chapel-lang.org
Other
1.79k stars 420 forks source link

Should Chapel (eventually) support chained comparisons? #24558

Open bradcray opened 8 months ago

bradcray commented 8 months ago

In #24155, we made expressions like a < b < c into syntax errors due to the ambiguity around whether the interpretation should be C-style (essentially (a < b): int < c) or Math/Python-style. Our thought was that rather than having people assuming Math/Python and getting C (which is what we've traditionally implemented), we should make it an error to reduce surprises.

[In addition to resolving this ambiguity, the change also pointed out some longstanding bugs in user code that assumed a < b | c < d would associate as (a < b) | (c < d), when in Chapel it's actually a < (b | c) < d based on feedback from users who believed C to have gotten this wrong for bitwise operators.]

In making a < b < c an error, some expressed interest in eventually supporting the Math/Python interpretation. This issue asks whether we should consider supporting that interpretation in the future.

bradcray commented 8 months ago

Notably, Rust does not support chaining operators, and the following resources may be helpful background on why:

damianmoz commented 8 months ago

I did a quick poll among a few mates with 200+ years experience (and an average over 20 years) and they all said they were not inconvenienced by the lack of chained comparisons in C/C++ and Fortran.

I and a colleague who have a meeting in a few minutes have programmed technical applications in Python for nearly years and we cannot remember when we used a chained comparison. But others' experience might be different

My 2c.

There are bigger fish to fry and other things with higher priority.

mppf commented 8 months ago

I'd like to see the math interpretation supported eventually. I also agree it's not a high priority.

kwaters4 commented 7 months ago

I have been looking into this on and off for a while and the more I think and play around with it, the more I think having the ability to do chained comparisons leads to many misleading and frustrating edge cases. While Python may support a mathematical interpretation today, it should (in my experience) feel uncomfortable and unintuitive from a typed language perspective.

I agree (a < b < c) reads easily, but when it comes to contact with a type system, the first comparison should resolve down to a bool, full stop. This is where the problems arise. If you come from Python, your expectation is different, if you have run across this before. Python does some "magic" here, but the nuance would not "read" easily if handed a existing code base. I think forcing users to be explicit a < b && b <c prevents many future headaches at the expense of a few extra key strokes. I think Rust got it right on this front.

I do not appreciate any advantage of supporting chained comparisons. I think simple is better in this case. I am open for suggestions, I may be missing some information.

As mentioned above, there are bigger fish to fry, but if any code base were handed to me I would clean all chained comparisons out to prevent errors as discovered (see above) by the recent syntax change in the Chapel language with respect to chained comparisons.

damianmoz commented 7 months ago

There are lots of interesting discussions on the web for a variety of languages. Besides that for Python which thoroughly details the advantages and disadvantages of this operator there are also those like:

https://www.reddit.com/r/ProgrammingLanguages/comments/n888as/would_you_prefer_support_chaining_of_comparison/

While I am all about readability, for integers a range object may be preferable. With floating point numbers, one always has to consider the case of NaN.

A floating point range comparison like

0 < abs(x) < inf

might be better expressed as

isFIniteNonZero(abs(x))

and so on.

In terms of bigger fish to fry, even #18629 has a higher priority than chaining, although that is a bigger job in itself. And I am not suggesting that #18629 should be considered until after 2.0.

I still think chaining has a low ROI even if its readability is high

bradcray commented 7 months ago

In a similar vein to @damianmoz's response (who beat me by ~10 minutes), another alternative to writing expressions like a <= b && b <= c would be to write them as (a..c).contains(b).

For this to feel like a complete solution, we'd probably want to introduce ranges of additional types, like reals (e.g., (0..<epsilon).contains(x)) and ranges that are open on their lower bounds (e.g., (a<..<c).contains(b) as proposed in https://github.com/chapel-lang/chapel/issues/15272).

damianmoz commented 7 months ago

The contains() method is far more verbose than in.

I personally would probably stick to relational operators with floating point types because floating point exceptions are already defined over them. Not sure whether that advice has universal applicability.

Also, a lot of my work (and colleagues) relates to ported code so we would probably stick to the same expression syntax that was used in the existing C/C++ or Fortran code to make side-by-side visual comparisons easier and ensure that we were not introducing bugs.

edits made - sorry

damianmoz commented 7 months ago

You need a way of handling inclusive or exclusive limits, e.g. mathematical

[ lower inclusive limit, upper exclusive limit )
( lower inclusive limit, upper inclusive limit ]

i.e. when finite positive non-zero is mathematically

x is in (0.0, Infinity) // 0 < x Infinity 
bradcray commented 7 months ago

@damianmoz : This is the concept I was alluding to above when writing:

For this to feel like a complete solution, we'd probably want ... ranges that are open on their lower bounds (e.g., (a<..<c).contains(b) as proposed in https://github.com/chapel-lang/chapel/issues/15272).

E.g., In that world, I'd write your example as (0.0<..<inf).contains(x)