Hacker News new | past | comments | ask | show | jobs | submit login

I'm not much of a Python guy, but that chained comparison operator is sweet!

Sure, it's just syntax sugar, but it saves a lot of keystrokes, especially if the variable name is long.

Is Python the only language with this feature?




Common Lisp (and other dialects):

   (< a b c d)   ;; T if a < b < c < d
   (<= a b c d)  ;; T if a < b < c < d
Also:

   (lcm a b c d ...) ;; lowest common multiple

   (+) -> 0
   (+ a) -> a
   (+ a b)  -> a + b
   (+ a b c) -> (a + b) + c

   (*) -> 1
   (* a) -> a
   (* a b) -> a * b
   (* a b c) -> (a * b) * c
Is it just syntactic sugar? (< a b c) evaluates a, b and c only once, which matters if they are expensive external function calls or have side effects.

   (and (< (foo) (bar))
        (< (bar) (baz)))
isn't the same as

   (< (foo) (bar) (baz))
By the way, this could be turned into a short-circuiting operator: more semantic variation. Suppose < is allowed to control evaluation. Then an expression like (< a b c d) could avoid evaluating the c and d terms, if the a < b comparison fails.


I love clojure for providing

   (<= 1 2 2 5 6 6 9)
and for making = actually useful (works on nested structures properly).

"Is this sorted", and "are these equal" are intuitive and useful concepts in programming and you shouldn't need to reimplement them each time you need them.


"Is this sorted" is useful but "<=" is not a good name for it.


Why? It's generalisation of binary operator <= to many arguments. Works as well with <, >, >=.


But can any lisp dialect do:

    a < b >= c

?


Yes, for instance in Common Lisp we can make ourselves a rel macro, such that

   (rel a < b >= c)
evaluates a, b, c once, left to right, and then performs the comparisons between the successive evaluated terms.

  $ cat rel.lisp 
  (defmacro rel (&rest args)
    (loop for expr in args by #'cddr
          for g = (gensym)
          collect g into gens
          collect `(,g ,expr) into lets
          finally (return `(let ,lets
                             (and
                               ,(loop for (left op right) on args by #'cddr
                                      for (lgen rgen) on gens
                                      while rgen
                                      collect `(,op ,lgen ,rgen)))))))

  $ clisp -q -i rel.lisp 
  ;; Loading file rel.lisp ...
  ;; Loaded file rel.lisp
  [1]> (macroexpand '(rel))
  (LET NIL (AND NIL)) ;
  T
  [2]> (macroexpand '(rel x))
  (LET ((#:G3219 X)) (AND NIL)) ;
  T
  [3]> (macroexpand '(rel x < y))
  (LET ((#:G3220 X) (#:G3221 Y)) (AND ((< #:G3220 #:G3221)))) ;
  T
  [4]> (macroexpand '(rel x < y >= z))
  (LET ((#:G3222 X) (#:G3223 Y) (#:G3224 Z))
   (AND ((< #:G3222 #:G3223) (>= #:G3223 #:G3224)))) ;
  T
  [5]> (macroexpand '(rel x < y >= z < w))
  (LET ((#:G3225 X) (#:G3226 Y) (#:G3227 Z) (#:G3228 W))
   (AND ((< #:G3225 #:G3226) (>= #:G3226 #:G3227) (< #:G3227 #:G3228)))) ;
  T
Could use some error checking, obviously, to make it a production-quality macro.


Anyone spot the bug? Of course

  (AND ((...) (...) ...)))
should be

  (AND (...) (...) ...)
I haven't run the generated code once, yet I can debug it: such is the power of the HN development environment.

The fix, of course, is to splice the comparison expressions into the AND:

  `(let ,lets
     (and
        ,@(loop for ... )))   ; comma splat, not comma


In a Lisp-1 dialect like Scheme, rel could easily and conveniently be a function. The call (rel a < b <= c) simply evaluates its arguments. The arguments < and <= are functions. The rel function then just operates on these values.


Given that lisps tend to use prefix notation, the example doesn't even translate meaningfully.


I assume the above was intended to be a Python expression. The answer, to the best of my knowledge, is "not really". There is no built in or reasonably standard function that lets you check simultaneously that a is less than b which in turn is greater than or equal to c. Not that we couldn't define one ad-hoc, although making it reusable in a way that's reasonably idiomatic might be a challenge...

Obviously, you can express it slightly more verbosely:

(and (< a b) (>= b c))


(< a (>= b c)) same number of operators but a few extra parens


This isn't the same thing -- the Python syntax is asking for a < b and b >= c, while only evaluating b once. There are many infix math libraries for lisps (e.g. this one from 1995: https://www.cs.cmu.edu/afs/cs/project/ai-repository/ai/lang/...) that allow things like #I(a < b >= c), but the trick is only evaluating b once. (The linked lib will expand into two evaluations if b is an expression.)


Here (>= b c) will return a boolean value, which you're then comparing to a.


Which means it could be done, but the relational operators (any one of which could be the leftmost constituent) have to all be defined as macros which analyze the rest of the expression with alternative evaluation rules.


I think syntactic sugar is vastly undervalued by programmers. Anything that lets me more naturally read & write code should lead to fewer bugs and more productive software development, as well as making programming more enjoyable.


It's not simply syntax sugar either.

    def sideEffects():
        print "Called."
        return 5

    if 0 < sideEffects() < 10:
        # sideEffects is only called once


SQL has “x BETWEEN y AND z” as a special case which does “y >= x >= z”, which in practice is quite often what you actually want to use this feature for.


> Is Python the only language with this feature?

No. See, e.g., http://stackoverflow.com/questions/4090845/language-support-...


I'm not sure if other languages have it, but I have to say that pycharm is excellent at suggesting chained comparisons to you. I didn't know this existed before I switched IDEs.

if 2 > 3 and 2 < 7

becomes

if 3 < 2 < 7


If this Pycharm really had brains it would tell you that (2>3 and 2<7) reduces to (false and true), to false, at compile time, so the code wrapped in the if is unreachable.


I'm not sure about pycharm, but i know for certain IntelliJ warns you about constant conditions like that. That said, he could have picked that example just to illustrate a point, rather than something it literally suggests.


Perl6 has it too

  > perl6
  > 3 < 4 < 5
  True


3 < 4 < 5 evaluates to true (well, 1) in C as well.

What does 5 > 4 > 3 give?




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: