<p>Some languages, such as Python, support chaining comparison operators, such that <code>x &lt; y &lt; z</code> is equivalent to <code>x &lt; y &amp;&amp; y &lt; z</code>. This is handy syntactic sugar, and since <code>(x &lt; y) &lt; z</code> (comparing the boolean result of <code>x &lt; y</code> to <code>z</code>) is almost never useful, it would seem to have little cost.</p>
<p>However, many modern languages lack support for this, including Rust, Go, and Kotlin.</p>
<p>What downsides/pitfalls do chained comparison operators have, and/or what justifications do existing languages cite in not including them?</p>

<syntax><operators><syntactic-sugar><comparison>
What are the downsides of supporting chained comparison operators?
CC BY-SA 4.0
2023-07-02T03:08:47.473Z
Related, probably not duplicate: [Unambiguous syntax for a ternary comparison operator](https://langdev.stackexchange.com/q/744/133)
Michael Homer
CC BY-SA 4.0
2023-07-02T03:30:52.830Z
I don't know if this counts as a pitfall, but `x < y && y < z` isn't quite equivalent because it evaluates `y` twice. If you do have this feature then you need to ensure that `y` is evaluated only once.
kaya3
CC BY-SA 4.0
2023-07-02T03:36:10.450Z
@kaya3-supportthestrike Another slight inequivalence is that you'd presumably want to ensure `x < z` too if you allow arbitrary implementations of comparison operators
rydwolf
CC BY-SA 4.0
2023-07-03T14:59:26.013Z
Instead of being syntactic sugar, `<`, `⩽` and friends could return their RHS wrapped in a type that can be cast to a boolean. Same applies to `=` and `≠`. In fact Goal-Oriented Languages like SNOBOL, Icon and Verse, where first-class success/failure logic replaces boolean logic, work this way and generalise it.
Longinus
<p>Generally speaking, I'd say it's always best if a feature is <a href="https://en.wikipedia.org/wiki/First-class_citizen" rel="noreferrer">first class</a> instead of ad-hoc. It's easy to have first-class (2-arg.-infix) comparison operators, but chained comparison operators can only be done as an ad-hoc rules in most languages.</p>
<p>What I mean here is, a normal <code>&lt;</code> operator is simply a function with type <code>(Int,Int) -&gt; Bool</code> (or some polymorphic version of this) and some infix precedence. None of this really requires special parser handling: infix operators are pretty much universal because (unless you're a Lisper) you certainly want <code>+</code> and <code>*</code> with the normal precedences.</p>
<p>That means it is also fairly obvious how these operators should be overloaded or passed to higher-order functions (as long as you know how that is generally done in your language). It's not impossible to do that kind of thing with chained operators, either, but it's certainly a lot less obvious what's actually going on. The way Python does it is <em>quite</em> ad-hoc, in that it desugars the chain of comparisons to binary calls to <code>__lt__</code> connected with <code>and</code> operators. Why use those in particular? What if somebody would like to use this with another type instead of <code>bool</code>? It gets all very confusing.</p>
<p>You might argue that the special case of simple boolean-valued comparisons is important, &quot;canonical&quot; enough to warrant special-case handling. Perhaps it is, but it's always best to make do without special cases.</p>
<p>I'd remark that there is at least one language that supports chained comparison &quot;naturally&quot;, without a special syntax rule: in <a href="https://dev.epicgames.com/documentation/en-us/uefn/verse-language-reference" rel="noreferrer">Verse</a>, there is no boolean type at all. Instead, comparison operators either <em>succeed</em> or <em>fail</em>, which are concepts the language has hard-baked, and having a chained comparison simply means you twice compare and each time it might fail.</p>
<p>The language designers themselves aren't quite sure whether that's just a cute quirk of the language, or actually a good feature...</p>
<p><a href="https://www.youtube.com/watch?v=832JF1o7Ck8?t=1262" rel="noreferrer">https://www.youtube.com/watch?v=832JF1o7Ck8?t=1262</a></p>
<p>Another thing I'd claim is that chained comparison are pretty much useful for only one thing: testing whether a number is within an interval. Sure that's a fairly common thing to do, but wouldn't it be better to extract that general concept? You just need an operator <code>∈</code> (or some ASCII rendition) which checks whether a variable is inside some set-like thing and a suitable class of set-like things including ranges (which for example Python <a href="https://docs.python.org/3/library/stdtypes.html#typesseq-range" rel="noreferrer">does in fact already have</a>).</p>

CC BY-SA 4.0
2023-07-02T18:13:13.457Z
2023-07-02T18:25:50.013Z
2023-07-02T18:25:50.013Z
leftaroundabout
CC BY-SA 4.0
2023-07-02T18:46:36.827Z
python's `b in range(a, c)` is only equivalent to `a < b < c` if `a,b,c` are all integers, though. There is no continuous range for floats. Module `sympy` does have an `Interval` type but it's heavy artillery.
Stef
CC BY-SA 4.0
2023-07-02T18:50:52.750Z
@Stef well, Python _has_ chained comparison and uses `range` mosly for looping. My point was that if a language really wanted to support interval checks in a nice way, it _could_ easily do that without chained comparisons.
leftaroundabout
CC BY-SA 4.0
2023-07-02T19:12:14.270Z
Isn't a `bool` just "succeed or fail"? I can't see any advantage whatsoever to allowing comparison operators to return non-`bool`s
rydwolf
CC BY-SA 4.0
2023-07-02T20:28:03.320Z
@RydwolfPrograms a bool is just _a type with two values_. What meaning is assigned to these values depends; often `true` is actually the failure-ish case (e.g. classical `0 = exit_success`). Also, whether you like it or not, comparison operators can and do often return things other than booleans. For example in Matlab and Numpy, comparisons of arrays/matrices give a _mask_ as the result. In proof assistants, comparison operators may yield a `Prop` rather than a `Bool`. Etc. etc..
leftaroundabout
CC BY-SA 4.0
2023-07-13T11:44:24.143Z
speaking of Lisp, comparison functions are variadic `(< 3)`, `(< 3 4)`, `(< 3 4 8 7)` so it is quite natural too
coredump
<p>Boolean equality is XNOR. <a href="https://github.com/bbrk24/lexifer-ts/blob/c5005aa82d605a5d3e918dcc689be7aac828efc8/src/rule.ts#L66" rel="nofollow noreferrer">I have (ab)used this before</a>; a language which allows <code>a == b == c</code> would not be able to exploit this.</p>

CC BY-SA 4.0
2023-07-02T03:47:06.390Z
2023-07-02T03:47:06.390Z
Bbrk24
CC BY-SA 4.0
2023-07-02T03:54:37.207Z
`!=` is XOR on Booleans, too.
kaya3
CC BY-SA 4.0
2023-07-02T04:04:39.257Z
You would still be able to do this right? You just need parens: `(a == b) == c`
rydwolf
<p>My inclination would be to have a comparison operator for type T yield a comparisonResult(T) which encapsulated a Boolean and a T, and have a comparison operator overload which accepts a comparisonResult(T) and an operationYielding(T).  For efficiency, operationYielding(T) should be a compiler-internal type distinct from functionReturning(T); functions accepting arguments of that type would generally have their code folded into the calling context in such a way as to avoid having to create any kind of closure object to access automatic objects in the parent context.</p>

CC BY-SA 4.0
2023-07-02T16:14:51.483Z
2023-07-02T16:14:51.483Z
supercat
CC BY-SA 4.0
2023-07-02T16:21:16.863Z
Does this mean that `let b = x > y;` would have `b` be some type other than Boolean? Or would this be contextual?
kaya3
CC BY-SA 4.0
2023-07-02T16:47:57.977Z
@kaya3-supportthestrike: Interesting question.  If Boolean types were not allowed to use comparison operators, then any use of `b` would either unambiguously require that the type coersion be performed (and its timing would be irrelevant), or that it not be performed (rendering the timing moot).  I think I'd handle the situation by specifying that `let c = b > z` would only be allowed when `z` was known to be a type that couldn't be compared to a Boolean but could be compared to `y`, and a compiler had kept the earlier ComparisonResult.  If code wanted to chain a comparison using `b`, ...
supercat
CC BY-SA 4.0
2023-07-02T16:53:46.973Z
...it would need to be written as something like `let c = cast(ComparisonResult(typeof y,b)(b) > z`, which would be rejected unless `b` was already that type, or as `cast(bool,b) > z`.  I subscribe to the idea that having compilers reject constructs whose intended meaning is ambiguous in the absence of directives that would set default behaviors, is better than having language designers try to guess what such constructs should mean.  Incidentally, I'd also (absent config directives) reject e.g. `double1 = float1*float2;` requiring either `double1 = (double)(float)(float1*float2);` or...
supercat
CC BY-SA 4.0
2023-07-02T16:56:23.493Z
...`double1 = (double)float1*float2;`.  Even if language rules were clear about how a compiler woudl interpret the original construt, and a human reading the code would have no trouble knowing how a compiler would interpret it, a human reading the code would often have no way of knowing *how the programmer who wrote the code was intending that it be processed*.  I'd regard the latter forms as superior to the former if the program cares about which method is chosen, and if a programmer wants "whichever is faster" semantics, there should be a way to indicate that.
supercat
<p>For what it's worth, modern C# allows this syntax as part of its pattern matching features:</p>
<pre class="lang-cs prettyprint-override"><code>public static bool IsLetterOrSeparator(this char c) =&gt;
    c is (&gt;= 'a' and &lt;= 'z') or (&gt;= 'A' and &lt;= 'Z') or '.' or ',';
</code></pre>
<p>That's an <em>extension method</em> (signified by the <code>this</code> keyword) that extends the <code>char</code> type.  It returns true if <code>c</code> is between <code>'a'</code> and <code>'z'</code> or between <code>'A'</code> and <code>'Z'</code> or a period or a comma.  Personally, I think it's more readable than the &quot;chained operators&quot; discussed here.</p>
<p>Coming from a C family background (C, C++ and C# - a touch of Java), I'd read <code>x &lt; y &lt; z &lt; w</code> as <code>((x &lt; y) &lt; z) &lt; w</code> which makes no sense to my eyes.</p>
<p>Reference: <a href="https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-9#pattern-matching-enhancements" rel="nofollow noreferrer">https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-9#pattern-matching-enhancements</a></p>

CC BY-SA 4.0
2023-07-08T02:55:34.743Z
2023-07-08T02:58:31.827Z
2023-07-08T02:58:31.827Z
Flydog57
<p>One quirky problem with chained comparisons is that they are deceptively difficult to implement (easy to implement approximately, but difficult to implement exactly). It might seem to you that you can simply rewrite <code>a &lt; b &lt; c</code> as <code>a &lt; b and b &lt; c</code>, but that's not the case: what if <code>b</code> is a function with side-effects? The generated assembly code would call it twice instead of once.</p>
<p>That problem might seem similar to the problem which occurs when compiling <code>+=</code> and similar operators, when incrementing an element of an array with the index being a function with side-effects, but that's actually not the same problem. There doesn't seem to be such a simple solution (of using a local temporary variable) to the problem with chained comparisons.</p>
<p>I've asked <a href="https://langdev.stackexchange.com/q/3755/330">a question about that problem</a>.</p>

CC BY-SA 4.0
2024-04-29T15:38:01.157Z
2024-04-29T15:38:01.157Z
FlatAssembler
CC BY-SA 4.0
2024-04-29T17:43:42.483Z
What do you mean by *"There doesn't seem to be such a simple solution (of using a local temporary variable) to the problem with chained comparisons."*? Of course you can use a temporary local variable to reuse the result of `b`. Why not?
kaya3
<p>In an imperative programming language, the <strong>operational semantics</strong> of n-ary comparison operators are tricky.</p>
<p>Say we have <code>a &lt; b &lt; c</code>, where <code>a</code>, <code>b</code> and <code>c</code> are expressions that may cause side effects. If we have <code>a &lt; b</code>, it's easy: evaluate <code>a</code>, evaluate <code>b</code>, and then see if the value of <code>a</code> is less than that of <code>b</code>. More formally, we write: if <span class="math-container">$\Delta \vdash a \Downarrow v; \Delta'$</span> and <span class="math-container">$\Delta' \vdash b \Downarrow v'; \Delta''$</span> , then <span class="math-container">$\Delta \vdash a &lt; b \Downarrow \mathtt{is\_true}(v &lt; v'); \Delta''$</span>.</p>
<p>This is what we call <em>big-step operational semantics</em>. <span class="math-container">$\Delta$</span> is just a store that maps variables to their values, and <span class="math-container">$\Delta \vdash e \Downarrow v; \Delta'$</span> here means that evaluating <span class="math-container">$e$</span> under the store <span class="math-container">$\Delta$</span> produces the value <span class="math-container">$v$</span> and a (possibly modified) store <span class="math-container">$\Delta'$</span>. So this is clear to most of us, and is what most of us would expect when we enter <code>a &lt; b</code>.</p>
<p>But when we have n-way comparisons, this becomes tricky. In what order are the expressions evaluated, and at what time do we compare them? If expressions may produce side effects, then this will affect the result of the comparison as well as the store environment after evaluation. Both of the following options seem quite reasonable for <code>a &lt; b &lt; c</code>:</p>
<ul>
<li>evaluate <code>a</code>, evaluate <code>b</code>, evaluate <code>c</code>, then perform value checks;</li>
<li>as syntactic sugar for <code>a &lt; b &amp;&amp; b &lt; c</code>, which means <code>b</code> will be evaluated twice, possibly in different environments.</li>
</ul>
<p>I'm sure you can come up with others. Unless you have a very well-defined operational semantics, this will just cause confusion everywhere.</p>
<p>However, if you are working in a <em>pure</em> programming language without any side effects, the order of evaluation cannot affect an expression's value, so semantically this is pretty safe. Some people will argue that this will create problems with parsing/surface syntax, but as long as we have a well-defined semantics, surface syntax is just a matter of taste.</p>

CC BY-SA 4.0
2024-05-01T09:39:26.340Z
2024-05-01T09:39:26.340Z
xuq01
<p>One thing I think Python got wrong in this regard, is that they made this work with too many &quot;comparison operations&quot;. Sure, <code>x &lt; y &lt; z</code> is common to write in mathematics, but <code>x &lt; y == z</code> looks kind of weird, and <code>x is y not in z</code> is probably not useful to anybody.</p>
<p>The reason I say Python got this wrong is that empirically, a lot of people are confused about it. Chained comparisons are one of the most recurring Python questions on Stack Overflow; currently, <a href="https://stackoverflow.com/q/6074018/12299000">the canonical question</a> has 145 upvotes and <a href="https://stackoverflow.com/questions/linked/6074018?lq=1">52 duplicates</a>. I think there are even more questions which are indirect duplicates. In almost all of those questions, the user has thought that <code>a OP b OP c</code> must mean either <code>(a OP b) OP c</code> or <code>a OP (b OP c)</code>, and been surprised that it means neither; understandably so, because it would be true for all other operators.</p>
<p>What could Python do differently? I think chained comparisons like <code>x &lt;= y &lt; z</code> make the most sense when the two comparisons are both inequalities in the same direction, either both <code>&lt;</code> or <code>&lt;=</code>, or both <code>&gt;</code> or <code>&gt;=</code>. Other comparison operators like <code>==</code>, <code>!=</code>, <code>in</code>, <code>not in</code>, <code>is</code> and <code>is not</code> should not get this treatment.</p>
<p>On the other hand, if chained comparisons are limited this way then most use-cases could be served instead by testing membership in a range or interval, e.g. <code>y in range(x, z)</code>. So, if you have suitable types in your standard library for representing intervals, then you might not need comparison chains at all. This still leaves longer chains like <code>x &lt; y &lt; z &lt; w</code>, and cases where computing <code>z</code> might be expensive so you want to skip computing it when <code>x &lt; y</code> is false; but these are edge-cases, so I wouldn't worry too much about them as a language designer.</p>
<p>This doesn't mean that <code>x &lt; y &lt; z</code> should mean <code>(x &lt; y) &lt; z</code> instead; as you mention, that's generally useless. My approach <a href="https://kaya3.github.io/MJr/notes/parser.html#operator-precedence" rel="noreferrer">in my language MJr</a> is to make this expression a syntax error: the comparison operators are neither left- nor right-associative. This requires no special treatment in the parser ─ just treat both operands like you treat the right operand of a left-associative operator or the left operand of a right-associative one ─ but I did decide to add <a href="https://github.com/kaya3/MJr/blob/049e49e845ff39443761fc11502fb3ef5538f5e4/src/frontend/parser.ts#L340" rel="noreferrer">a specific error message</a> to say that chained comparisons are not supported.</p>

CC BY-SA 4.0
2023-07-02T03:24:12.703Z
2023-07-02T17:15:36.377Z
2023-07-02T17:15:36.377Z
kaya3
CC BY-SA 4.0
2023-07-02T03:32:02.550Z
I disagree when you say that `==`/`is` should be excluded. Sometimes, it can be useful to check that `a = b = c`, and I don’t like having to arbitrarily choose between `a = b AND b = c` vs `a = b AND a = c`. (I’ve heard that in some SQL implementations, which pairs you choose can drastically affect performance.)
Bbrk24
CC BY-SA 4.0
2023-07-02T03:34:07.710Z
@Bbrk24 The problem is that beginners often write `x < y is True` or `x < y == True`, which would be correct if it meant `(x < y) is True`, but it doesn't.
kaya3
CC BY-SA 4.0
2023-07-02T03:36:10.693Z
Then just make it its own group, the same way you suggest separating `<` from `>`.
Bbrk24
CC BY-SA 4.0
2023-07-02T03:38:47.473Z
@Bbrk24 You could, but you would have the same problem with `x == y is True`, or `x in y in z` where `z` is a list of booleans (yes, I have seen someone write that and wonder why it didn't work). The case for `x == y == z` is strongest, but personally I don't find anything wrong with `x == y and y == z`; or you can write `len({x, y, z}) == 1`.
kaya3
CC BY-SA 4.0
2023-07-02T04:06:44.863Z
@kaya3-supportthestrike The former is longer and may necessitate an intermediate variable if `y` is an expression. It's also prone to nasty bugs if you mistype one `y` but not the other. The latter is an awful looking hack IMO.
rydwolf
CC BY-SA 4.0
2023-07-02T11:28:33.453Z
`y in range(x, z)` is okay, but it lacks the syntactic clarity of `x<=y<z` and immediately becomes fiddly and ugly if you want `x < y <= z` or whichever variant isn't immediately covered by whether the particularly implementation of `range` is open, closed, or half-open.
Jack Aidley
CC BY-SA 4.0
2023-07-02T13:18:52.633Z
@JackAidley On syntactic clarity, as I've said, empirically the syntax is unclear to a lot of users. On range syntax, Rust handles this by having `a..b` be a half-open range, and `a..=b` be closed; that covers most practical cases for integers, but other syntaxes are imaginable for range/interval constructors, and ranges/intervals are useful for other things than membership tests anyway so it's good for the language to have them.
kaya3
CC BY-SA 4.0
2023-07-02T14:40:41.093Z
@kaya3-supportthestrike I don't believe it's syntactically unclear. I've seen many beginners in less expressive languages asking how to write the same thing in the less expressive `x < y && y < z` format, although I'd agree that `x < y == z` is unclear and the similar problems with your other examples.
Jack Aidley
CC BY-SA 4.0
2023-07-02T14:41:22.853Z
@kaya3-supportthestrike I'm not familiar with Rust, but I think that's neat syntax. It doesn't solve wanting `x < y <= z` though, does it?
Jack Aidley
CC BY-SA 4.0
2023-07-02T14:56:58.080Z
@JackAidley As I said, other syntaxes are imaginable for range/interval constructors, and ranges/intervals are useful for other things than membership tests anyway so it's good for the language to have them.
kaya3
CC BY-SA 4.0
2023-07-02T15:38:24.437Z
@kaya3-supportthestrike I agree they're useful. I don't agree they are a good alternative to the clear, clean syntax of chained operators.
Jack Aidley
CC BY-SA 4.0
2023-07-02T15:46:27.197Z
@JackAidley You are free to believe that chained comparisons are a clear, clean syntax, but you are arguing against evidence which shows that many users find it confusing, not clear. If you have evidence or reasoning to support your opinion then feel free to write an answer presenting it.
kaya3
CC BY-SA 4.0
2023-07-02T17:00:35.080Z
It's also common to write `x < y == z` in mathematics, but more in "chains" of expressions than in conditions.
md2perpe
CC BY-SA 4.0
2023-07-25T07:16:56.553Z
I would say the problem isn't so much that Python supports chaining as many kinds of comparisons as it does, but rather that it allows *mixed* comparison chains. `x is y is z` is perfectly clear and useful, in a way that `x is y < z` isn't. Although I certainly can see an argument that `not in` shouldn't be involved in chaining at all, and maybe `in` shouldn't either. As for workarounds like `y in range(x, z)`, that doesn't work for floating-point numbers as is, and `range` used to be a utility function creating a list and potentially wasting huge amounts of temporary memory.
Karl Knechtel
CC BY-SA 4.0
2023-07-25T13:39:02.060Z
@KarlKnechtel Indeed, you would need more standard library types like `interval(x, z)` to represent intervals of floats, if you wanted to do this with more than just `x <= y < z` for integers. I think for `is` and `==` the case is strongest, but `x == y == True` and `x is y is True` are still going to be written by beginners who will get confused by it.
kaya3
CC BY-SA 4.0
2023-07-25T19:33:41.383Z
Ah, well, comparison to boolean literals is a code smell that can itself be detected and warned about, whether or not chaining is involved.
Karl Knechtel
# What are the downsides of supporting chained comparison operators?
Posted by: username_0 on 2023-07-02
Some languages, such as Python, support chaining comparison operators, such that `x < y < z` is equivalent to `x < y && y < z`. This is handy syntactic sugar, and since `(x < y) < z` (comparing the boolean result of `x < y` to `z`) is almost never useful, it would seem to have little cost.

However, many modern languages lack support for this, including Rust, Go, and Kotlin.

What downsides/pitfalls do chained comparison operators have, and/or what justifications do existing languages cite in not including them?
**Tags:** syntax operators syntactic-sugar comparison
> **Comments:**
> **username_1**: Related, probably not duplicate: [Unambiguous syntax for a ternary comparison operator](https://langdev.stackexchange.com/q/744/133)  
> **username_2**: I don't know if this counts as a pitfall, but `x < y && y < z` isn't quite equivalent because it evaluates `y` twice. If you do have this feature then you need to ensure that `y` is evaluated only once.  
> **username_0**: @kaya3-supportthestrike Another slight inequivalence is that you'd presumably want to ensure `x < z` too if you allow arbitrary implementations of comparison operators  
> **username_3**: Instead of being syntactic sugar, `<`, `⩽` and friends could return their RHS wrapped in a type that can be cast to a boolean. Same applies to `=` and `≠`. In fact Goal-Oriented Languages like SNOBOL, Icon and Verse, where first-class success/failure logic replaces boolean logic, work this way and generalise it.  
---
## 7 answers
### Answer by username_2 on 2023-07-02. Score: 11
One thing I think Python got wrong in this regard, is that they made this work with too many "comparison operations". Sure, `x < y < z` is common to write in mathematics, but `x < y == z` looks kind of weird, and `x is y not in z` is probably not useful to anybody.

The reason I say Python got this wrong is that empirically, a lot of people are confused about it. Chained comparisons are one of the most recurring Python questions on Stack Overflow; currently, [the canonical question](https://stackoverflow.com/q/6074018/12299000) has 145 upvotes and [52 duplicates](https://stackoverflow.com/questions/linked/6074018?lq=1). I think there are even more questions which are indirect duplicates. In almost all of those questions, the user has thought that `a OP b OP c` must mean either `(a OP b) OP c` or `a OP (b OP c)`, and been surprised that it means neither; understandably so, because it would be true for all other operators.

What could Python do differently? I think chained comparisons like `x <= y < z` make the most sense when the two comparisons are both inequalities in the same direction, either both `<` or `<=`, or both `>` or `>=`. Other comparison operators like `==`, `!=`, `in`, `not in`, `is` and `is not` should not get this treatment.

On the other hand, if chained comparisons are limited this way then most use-cases could be served instead by testing membership in a range or interval, e.g. `y in range(x, z)`. So, if you have suitable types in your standard library for representing intervals, then you might not need comparison chains at all. This still leaves longer chains like `x < y < z < w`, and cases where computing `z` might be expensive so you want to skip computing it when `x < y` is false; but these are edge-cases, so I wouldn't worry too much about them as a language designer.

This doesn't mean that `x < y < z` should mean `(x < y) < z` instead; as you mention, that's generally useless. My approach [in my language MJr](https://kaya3.github.io/MJr/notes/parser.html#operator-precedence) is to make this expression a syntax error: the comparison operators are neither left- nor right-associative. This requires no special treatment in the parser ─ just treat both operands like you treat the right operand of a left-associative operator or the left operand of a right-associative one ─ but I did decide to add [a specific error message](https://github.com/kaya3/MJr/blob/049e49e845ff39443761fc11502fb3ef5538f5e4/src/frontend/parser.ts#L340) to say that chained comparisons are not supported.
> **Comments:**
> **username_7** (2023-07-02): I disagree when you say that `==`/`is` should be excluded. Sometimes, it can be useful to check that `a = b = c`, and I don’t like having to arbitrarily choose between `a = b AND b = c` vs `a = b AND a = c`. (I’ve heard that in some SQL implementations, which pairs you choose can drastically affect performance.)  
> **username_2** (2023-07-02): @Bbrk24 The problem is that beginners often write `x < y is True` or `x < y == True`, which would be correct if it meant `(x < y) is True`, but it doesn't.  
> **username_7** (2023-07-02): Then just make it its own group, the same way you suggest separating `<` from `>`.  
> **username_2** (2023-07-02): @Bbrk24 You could, but you would have the same problem with `x == y is True`, or `x in y in z` where `z` is a list of booleans (yes, I have seen someone write that and wonder why it didn't work). The case for `x == y == z` is strongest, but personally I don't find anything wrong with `x == y and y == z`; or you can write `len({x, y, z}) == 1`.  
> **username_0** (2023-07-02): @kaya3-supportthestrike The former is longer and may necessitate an intermediate variable if `y` is an expression. It's also prone to nasty bugs if you mistype one `y` but not the other. The latter is an awful looking hack IMO.  
> **username_12** (2023-07-02): `y in range(x, z)` is okay, but it lacks the syntactic clarity of `x<=y<z` and immediately becomes fiddly and ugly if you want `x < y <= z` or whichever variant isn't immediately covered by whether the particularly implementation of `range` is open, closed, or half-open.  
> **username_2** (2023-07-02): @JackAidley On syntactic clarity, as I've said, empirically the syntax is unclear to a lot of users. On range syntax, Rust handles this by having `a..b` be a half-open range, and `a..=b` be closed; that covers most practical cases for integers, but other syntaxes are imaginable for range/interval constructors, and ranges/intervals are useful for other things than membership tests anyway so it's good for the language to have them.  
> **username_12** (2023-07-02): @kaya3-supportthestrike I don't believe it's syntactically unclear. I've seen many beginners in less expressive languages asking how to write the same thing in the less expressive `x < y && y < z` format, although I'd agree that `x < y == z` is unclear and the similar problems with your other examples.  
> **username_12** (2023-07-02): @kaya3-supportthestrike I'm not familiar with Rust, but I think that's neat syntax. It doesn't solve wanting `x < y <= z` though, does it?  
> **username_2** (2023-07-02): @JackAidley As I said, other syntaxes are imaginable for range/interval constructors, and ranges/intervals are useful for other things than membership tests anyway so it's good for the language to have them.  
> **username_12** (2023-07-02): @kaya3-supportthestrike I agree they're useful. I don't agree they are a good alternative to the clear, clean syntax of chained operators.  
> **username_2** (2023-07-02): @JackAidley You are free to believe that chained comparisons are a clear, clean syntax, but you are arguing against evidence which shows that many users find it confusing, not clear. If you have evidence or reasoning to support your opinion then feel free to write an answer presenting it.  
> **username_13** (2023-07-02): It's also common to write `x < y == z` in mathematics, but more in "chains" of expressions than in conditions.  
> **username_14** (2023-07-25): I would say the problem isn't so much that Python supports chaining as many kinds of comparisons as it does, but rather that it allows *mixed* comparison chains. `x is y is z` is perfectly clear and useful, in a way that `x is y < z` isn't. Although I certainly can see an argument that `not in` shouldn't be involved in chaining at all, and maybe `in` shouldn't either. As for workarounds like `y in range(x, z)`, that doesn't work for floating-point numbers as is, and `range` used to be a utility function creating a list and potentially wasting huge amounts of temporary memory.  
> **username_2** (2023-07-25): @KarlKnechtel Indeed, you would need more standard library types like `interval(x, z)` to represent intervals of floats, if you wanted to do this with more than just `x <= y < z` for integers. I think for `is` and `==` the case is strongest, but `x == y == True` and `x is y is True` are still going to be written by beginners who will get confused by it.  
> **username_14** (2023-07-25): Ah, well, comparison to boolean literals is a code smell that can itself be detected and warned about, whether or not chaining is involved.  
---
### Answer by username_4 on 2023-07-02. Score: 8
Generally speaking, I'd say it's always best if a feature is [first class](https://en.wikipedia.org/wiki/First-class_citizen) instead of ad-hoc. It's easy to have first-class (2-arg.-infix) comparison operators, but chained comparison operators can only be done as an ad-hoc rules in most languages.

What I mean here is, a normal `<` operator is simply a function with type `(Int,Int) -> Bool` (or some polymorphic version of this) and some infix precedence. None of this really requires special parser handling: infix operators are pretty much universal because (unless you're a Lisper) you certainly want `+` and `*` with the normal precedences.

That means it is also fairly obvious how these operators should be overloaded or passed to higher-order functions (as long as you know how that is generally done in your language). It's not impossible to do that kind of thing with chained operators, either, but it's certainly a lot less obvious what's actually going on. The way Python does it is *quite* ad-hoc, in that it desugars the chain of comparisons to binary calls to `__lt__` connected with `and` operators. Why use those in particular? What if somebody would like to use this with another type instead of `bool`? It gets all very confusing.

You might argue that the special case of simple boolean-valued comparisons is important, "canonical" enough to warrant special-case handling. Perhaps it is, but it's always best to make do without special cases.

I'd remark that there is at least one language that supports chained comparison "naturally", without a special syntax rule: in [Verse](https://dev.epicgames.com/documentation/en-us/uefn/verse-language-reference), there is no boolean type at all. Instead, comparison operators either *succeed* or *fail*, which are concepts the language has hard-baked, and having a chained comparison simply means you twice compare and each time it might fail.

The language designers themselves aren't quite sure whether that's just a cute quirk of the language, or actually a good feature...

<https://www.youtube.com/watch?v=832JF1o7Ck8?t=1262>

Another thing I'd claim is that chained comparison are pretty much useful for only one thing: testing whether a number is within an interval. Sure that's a fairly common thing to do, but wouldn't it be better to extract that general concept? You just need an operator `∈` (or some ASCII rendition) which checks whether a variable is inside some set-like thing and a suitable class of set-like things including ranges (which for example Python [does in fact already have](https://docs.python.org/3/library/stdtypes.html#typesseq-range)).
> **Comments:**
> **username_5** (2023-07-02): python's `b in range(a, c)` is only equivalent to `a < b < c` if `a,b,c` are all integers, though. There is no continuous range for floats. Module `sympy` does have an `Interval` type but it's heavy artillery.  
> **username_4** (2023-07-02): @Stef well, Python _has_ chained comparison and uses `range` mosly for looping. My point was that if a language really wanted to support interval checks in a nice way, it _could_ easily do that without chained comparisons.  
> **username_0** (2023-07-02): Isn't a `bool` just "succeed or fail"? I can't see any advantage whatsoever to allowing comparison operators to return non-`bool`s  
> **username_4** (2023-07-02): @RydwolfPrograms a bool is just _a type with two values_. What meaning is assigned to these values depends; often `true` is actually the failure-ish case (e.g. classical `0 = exit_success`). Also, whether you like it or not, comparison operators can and do often return things other than booleans. For example in Matlab and Numpy, comparisons of arrays/matrices give a _mask_ as the result. In proof assistants, comparison operators may yield a `Prop` rather than a `Bool`. Etc. etc..  
> **username_6** (2023-07-13): speaking of Lisp, comparison functions are variadic `(< 3)`, `(< 3 4)`, `(< 3 4 8 7)` so it is quite natural too  
---
### Answer by username_11 on 2024-05-01. Score: 3
In an imperative programming language, the **operational semantics** of n-ary comparison operators are tricky.

Say we have `a < b < c`, where `a`, `b` and `c` are expressions that may cause side effects. If we have `a < b`, it's easy: evaluate `a`, evaluate `b`, and then see if the value of `a` is less than that of `b`. More formally, we write: if $\Delta \vdash a \Downarrow v; \Delta'$ and $\Delta' \vdash b \Downarrow v'; \Delta''$ , then $\Delta \vdash a < b \Downarrow \mathtt{is\\_true}(v < v'); \Delta''$.

This is what we call *big-step operational semantics*. $\Delta$ is just a store that maps variables to their values, and $\Delta \vdash e \Downarrow v; \Delta'$ here means that evaluating $e$ under the store $\Delta$ produces the value $v$ and a (possibly modified) store $\Delta'$. So this is clear to most of us, and is what most of us would expect when we enter `a < b`.

But when we have n-way comparisons, this becomes tricky. In what order are the expressions evaluated, and at what time do we compare them? If expressions may produce side effects, then this will affect the result of the comparison as well as the store environment after evaluation. Both of the following options seem quite reasonable for `a < b < c`:

* evaluate `a`, evaluate `b`, evaluate `c`, then perform value checks;
* as syntactic sugar for `a < b && b < c`, which means `b` will be evaluated twice, possibly in different environments.

I'm sure you can come up with others. Unless you have a very well-defined operational semantics, this will just cause confusion everywhere.

However, if you are working in a *pure* programming language without any side effects, the order of evaluation cannot affect an expression's value, so semantically this is pretty safe. Some people will argue that this will create problems with parsing/surface syntax, but as long as we have a well-defined semantics, surface syntax is just a matter of taste.
---
### Answer by username_7 on 2023-07-02. Score: 2
Boolean equality is XNOR. [I have (ab)used this before](https://github.com/bbrk24/lexifer-ts/blob/c5005aa82d605a5d3e918dcc689be7aac828efc8/src/rule.ts#L66); a language which allows `a == b == c` would not be able to exploit this.
> **Comments:**
> **username_2** (2023-07-02): `!=` is XOR on Booleans, too.  
> **username_0** (2023-07-02): You would still be able to do this right? You just need parens: `(a == b) == c`  
---
### Answer by username_8 on 2023-07-02. Score: 2
My inclination would be to have a comparison operator for type T yield a comparisonResult(T) which encapsulated a Boolean and a T, and have a comparison operator overload which accepts a comparisonResult(T) and an operationYielding(T). For efficiency, operationYielding(T) should be a compiler-internal type distinct from functionReturning(T); functions accepting arguments of that type would generally have their code folded into the calling context in such a way as to avoid having to create any kind of closure object to access automatic objects in the parent context.
> **Comments:**
> **username_2** (2023-07-02): Does this mean that `let b = x > y;` would have `b` be some type other than Boolean? Or would this be contextual?  
> **username_8** (2023-07-02): @kaya3-supportthestrike: Interesting question.  If Boolean types were not allowed to use comparison operators, then any use of `b` would either unambiguously require that the type coersion be performed (and its timing would be irrelevant), or that it not be performed (rendering the timing moot).  I think I'd handle the situation by specifying that `let c = b > z` would only be allowed when `z` was known to be a type that couldn't be compared to a Boolean but could be compared to `y`, and a compiler had kept the earlier ComparisonResult.  If code wanted to chain a comparison using `b`, ...  
> **username_8** (2023-07-02): ...it would need to be written as something like `let c = cast(ComparisonResult(typeof y,b)(b) > z`, which would be rejected unless `b` was already that type, or as `cast(bool,b) > z`.  I subscribe to the idea that having compilers reject constructs whose intended meaning is ambiguous in the absence of directives that would set default behaviors, is better than having language designers try to guess what such constructs should mean.  Incidentally, I'd also (absent config directives) reject e.g. `double1 = float1*float2;` requiring either `double1 = (double)(float)(float1*float2);` or...  
> **username_8** (2023-07-02): ...`double1 = (double)float1*float2;`.  Even if language rules were clear about how a compiler woudl interpret the original construt, and a human reading the code would have no trouble knowing how a compiler would interpret it, a human reading the code would often have no way of knowing *how the programmer who wrote the code was intending that it be processed*.  I'd regard the latter forms as superior to the former if the program cares about which method is chosen, and if a programmer wants "whichever is faster" semantics, there should be a way to indicate that.  
---
### Answer by username_9 on 2023-07-08. Score: 2
For what it's worth, modern C# allows this syntax as part of its pattern matching features:
public static bool IsLetterOrSeparator(this char c) =>
    c is (>= 'a' and <= 'z') or (>= 'A' and <= 'Z') or '.' or ',';

That's an extension method (signified by the this keyword) that extends the char type.  It returns true if c is between 'a' and 'z' or between 'A' and 'Z' or a period or a comma.  Personally, I think it's more readable than the "chained operators" discussed here.
Coming from a C family background (C, C++ and C# - a touch of Java), I'd read x < y < z < w as ((x < y) < z) < w which makes no sense to my eyes.
Reference: https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-9#pattern-matching-enhancements
---
### Answer by username_10 on 2024-04-29. Score: 0
One quirky problem with chained comparisons is that they are deceptively difficult to implement (easy to implement approximately, but difficult to implement exactly). It might seem to you that you can simply rewrite `a < b < c` as `a < b and b < c`, but that's not the case: what if `b` is a function with side-effects? The generated assembly code would call it twice instead of once.

That problem might seem similar to the problem which occurs when compiling `+=` and similar operators, when incrementing an element of an array with the index being a function with side-effects, but that's actually not the same problem. There doesn't seem to be such a simple solution (of using a local temporary variable) to the problem with chained comparisons.

I've asked [a question about that problem](https://langdev.stackexchange.com/q/3755/330).
> **Comments:**
> **username_2** (2024-04-29): What do you mean by *"There doesn't seem to be such a simple solution (of using a local temporary variable) to the problem with chained comparisons."*? Of course you can use a temporary local variable to reuse the result of `b`. Why not?  
---
