Python-like chained comparison operators

That's why I'm saying that technically, it isn't breaking, but it's a very surprising new kind of behavior.

2 Likes

Counter point: I would use this operator at least once a week at work.

1 Like

I will note that heterogeneous chained comparison operators (e.g. a < b > c) are incompatible with the proposal to disambiguate generic arguments to remove the need for the foo::<..>(..) syntax, though I think their lack of readability is already a strong counterpoint.

13 Likes

Good point; that's another good reason to only allow consistent comparison direction in one expression.

Note, though, that chained comparison isn't the only way to get such ambiguity issues: should a<b>>c parse as a < (b >> c) or (a<b>) > c?

3 Likes

Neither of those involves chained comparisons, and a < b >> c is already valid Rust code. Chained comparison operators would not change this.

2 Likes

Sorry, poor phrasing on my part. I meant that chained comparison (even with mixed comparison directions) is not the only way to get such ambiguity. I've edited the original message for clarity.

Chaining == sounds fine and useful to me. For !=, though, I perceive some ambiguity. If I were to read a != b != c in pseudocode, I would interpret that as meaning a, b, and c are all different. But if it desugared the same way as other chained comparisons, as a != b && b != c, it would evaluate to true even if a == c.

2 Likes

That's the reason why I also thought that == could be chained, while I don't see it reasonable for !=. However, @atagunov had a good point:

My bad, I didn't realize that – and I've perhaps never seen that either, although I admit I myself probably used something like (a == b) == (c == d), but it feels so different with the parentheses…

When taking all these points into account, I don't consider chaining operators a good idea anymore, even in the seemingly simple cases.

5 Likes

Some evil mind might even want to write

(a <= b) <= (c <= d)

which tests whether

a ≤ bc ≤ d
where “⇒” means “implies”

Yes, by its logic table, the arrow-like looking <= on bool, as it is implemented in bool: Ord, is the same as a (non short-circuiting) implication arrow the other way.

4 Likes

Not my point though :slight_smile:
My point: it's not obvious, there is learning to do

I think this is the original interpretation which was forbidden, just like

let a = 3;
let b = 4;
let c = true;

a <= b <= c // this is (a <= b) <= c

I would consider ordering bool-s bad form anyway. So to me the interpretation above seems contrived.

1 Like

Maybe the lack of obviousness alone is a good reason not to add chaining. Had we been talking about some new powerful and revolutionary feature, the learning might be justified, but this bit of syntax sugar would likely be rarely used anyway, which is a compelling argument for opting for macros instead, eg. chained!(a<b<c<d).

It would also be more flexible, since the hard decision-making could be offloaded to one or more 3rd-party macro libraries. Python, being a scripting language at heart, can justify adding such stuff in the core language, but I don't think this should be the case for Rust.

I also find @scottmcm's alternatives already compelling for many cases, and more in line with Rust's functional tendencies. Maybe some sort of syntax sugar for terser functional code would find wide enough use to warrant extending the core language, eg (the ! could be replaced by some other unambiguous sigil):

| !_ < x |            // => |a| a < x
|!   + x |            // => ditto, but the lack of _ permits an alternate interpretation
|!+|                  // => |a,b| a+b
| f(g( h(!_) + 2 )) | // => |a| f(g( h(a) + 2 ))

Which would allow stuff like:

iter.fold_first(|! * |);
iter.map(| f(g(h(!_)+2)) |);

(The |!+x| would be broadly equivalent to Haskell's (+x) syntax)

1 Like

This is how I feel too after seeing all the discussion. Comparison chaining is a helpful feature and I use it very often in Python, but imo one of the best parts of Rust (that Python doesn't share) is that it's highly predictable. Clearly the behavior of this syntax isn't obvious to at least some people, and I'd rather not add a piece of syntax sugar to the language if it makes things harder to understand even for a small portion of users.

7 Likes

Of course 2. <= 3.4 <= 6. is not allowed. As 2. <= 3.4 is true, what is the meaning of true <= 6.? If you want to avoid to repeat 3.4, you can write (2. ..6.).contains(&3.4) or (2. ..=6.).contains(&3.4) (well, the lower end is included anyway).

And what if you write a < b < c < d, or a < b > c?

I think that introducing the four ternary operators </<, </<=, <=/<, <=/<= would complicate the language with very little savings in source code size and no improvements in performance.

1 Like

Philosophically I think composability of expressions (i.e., the expression x should mean the same thing, regardless of whether it is a subexpression of some other expression y) is an important language design goal, and chained operators completely breaks that.

In this proposal, the substring a < b of a < b < c means something completely different from the expression a < b on its own. I would find this extremely confusing and inelegant.

5 Likes

In an educational context, I think I've seen more people confused when this doesn't work than when it does. It's a straightforward way to write pseudocode and there are no technical reasons it cannot work in the language. I frankly don't believe the syntax we would be enabling

if 0 < x <= 10 {
    ...
}

Is particularly confusing to anyone on this thread.

1 Like

Note that there's at least one counterexample in Rust today: x[i] behaves differently between let a = x[i]; and x[i] = 4;.

Maybe it would make sense to phrase it about being able to parenthesize subexpressions? I'm not sure what a < (b < c) would mean with this feature, for example.

5 Likes

Good point. But I think the diversity of meanings of x[i] was a necessary evil to accommodate muscle memory for people used to C, C++, etc. Also, it's pervasive enough that (1) it's feasible to learn, and (2) the space saved by the syntax sugar has a bigger payoff. Whereas a < b < c strikes me as much more niche.

It's confusing to me.

This seems like one of those things that should start out as a user crate macro, i.e. if some_macro!(0 < x < 10) { ... }. That seems like the best way to form an experience-based opinion.

2 Likes