Overhaul semantics of binary operators (#1261)

I've added shadow versions of many operators that didn't have one. And, I've changed the behavior of various binary operators with more or fewer than 2 arguments to make the shadow and real versions more consistent and to make the behavior more logical in either case. For details, see the additions to NEWS and the new file tests/native_tests/operators.hy, which simultaneously tests shadow and real operators.

Although there are a lot of changes, I've put them all in one commit because they're interdependent.
This commit is contained in:
Kodi Arfer 2017-04-13 17:42:01 -07:00 committed by Ryan Gonzalez
parent 18acfe6495
commit 5eb928356a
8 changed files with 474 additions and 235 deletions

7
NEWS
View File

@ -7,11 +7,18 @@ Changes from 0.12.1
* Added bytestring literals, which create `bytes` objects under Python 3
and `str` objects under Python 2
* Commas and underscores are allowed in numeric literals
* Many more operators (e.g., `**`, `//`, `not`, `in`) can be used
as first-class functions
* The semantics of binary operators when applied to fewer or more
than two arguments have been made more logical
* `(** a b c d)` is now equivalent to `(** a (** b (** c d)))`,
not `(** (** (** a b) c) d)`
* `setv` always returns None
* xor: If exactly one argument is true, return it
* hy.core.reserved is now hy.extra.reserved
[ Bug Fixes ]
* All shadowed operators have the same arities as real operators
* Shadowed comparison operators now use `and` instead of `&`
for chained comparisons
* partition no longer prematurely exhausts input iterators

View File

@ -371,7 +371,7 @@ def checkargs(exact=None, min=None, max=None, even=None, multiple=None):
class HyASTCompiler(object):
def __init__(self, module_name):
self.allow_builtins = False
self.allow_builtins = module_name.startswith("hy.core")
self.anon_fn_count = 0
self.anon_var_count = 0
self.imports = defaultdict(set)
@ -1876,7 +1876,7 @@ class HyASTCompiler(object):
col_offset=e.start_column)
@builds("=")
@builds("!=")
@builds("is")
@builds("<")
@builds("<=")
@builds(">")
@ -1884,33 +1884,25 @@ class HyASTCompiler(object):
@checkargs(min=1)
def compile_compare_op_expression(self, expression):
if len(expression) == 2:
rval = "True"
if expression[0] == "!=":
rval = "False"
return ast.Name(id=rval,
return ast.Name(id="True",
ctx=ast.Load(),
lineno=expression.start_line,
col_offset=expression.start_column)
return self._compile_compare_op_expression(expression)
@builds("is")
@builds("in")
@builds("!=")
@builds("is_not")
@builds("not_in")
@checkargs(min=2)
def compile_compare_op_expression_coll(self, expression):
return self._compile_compare_op_expression(expression)
@builds("%")
@builds("**")
@builds("<<")
@builds(">>")
@builds("|")
@builds("^")
@builds("&")
@builds_if("@", PY35)
@checkargs(min=2)
def compile_maths_expression(self, expression):
@builds("in")
@builds("not_in")
@checkargs(2)
def compile_compare_op_expression_binary(self, expression):
return self._compile_compare_op_expression(expression)
def _compile_maths_expression(self, expression):
ops = {"+": ast.Add,
"/": ast.Div,
"//": ast.FloorDiv,
@ -1926,14 +1918,18 @@ class HyASTCompiler(object):
if PY35:
ops.update({"@": ast.MatMult})
inv = expression.pop(0)
op = ops[inv]
op = ops[expression.pop(0)]
right_associative = op == ast.Pow
if right_associative:
expression = expression[::-1]
ret = self.compile(expression.pop(0))
for child in expression:
left_expr = ret.force_expr
ret += self.compile(child)
right_expr = ret.force_expr
if right_associative:
left_expr, right_expr = right_expr, left_expr
ret += ast.BinOp(left=left_expr,
op=op(),
right=right_expr,
@ -1941,27 +1937,46 @@ class HyASTCompiler(object):
col_offset=child.start_column)
return ret
@builds("*")
@builds("/")
@builds("**")
@builds("//")
@builds("<<")
@builds(">>")
@builds("&")
@checkargs(min=2)
def compile_maths_expression_2_or_more(self, expression):
return self._compile_maths_expression(expression)
@builds("%")
@builds("^")
@checkargs(2)
def compile_maths_expression_exactly_2(self, expression):
return self._compile_maths_expression(expression)
@builds("*")
@builds("|")
def compile_maths_expression_mul(self, expression):
if len(expression) > 2:
return self.compile_maths_expression(expression)
id_elem = {"*": 1, "|": 0}[expression[0]]
if len(expression) == 1:
return ast.Num(n=long_type(id_elem),
lineno=expression.start_line,
col_offset=expression.start_column)
elif len(expression) == 2:
return self.compile(expression[1])
else:
id_op = {"*": HyInteger(1), "/": HyInteger(1), "//": HyInteger(1)}
return self._compile_maths_expression(expression)
op = expression.pop(0)
arg = expression.pop(0) if expression else id_op[op]
expr = HyExpression([
HySymbol(op),
id_op[op],
arg
]).replace(expression)
return self.compile_maths_expression(expr)
@builds("/")
@checkargs(min=1)
def compile_maths_expression_div(self, expression):
if len(expression) == 2:
expression = HyExpression([HySymbol("/"),
HyInteger(1),
expression[1]]).replace(expression)
return self._compile_maths_expression(expression)
def compile_maths_expression_additive(self, expression):
def _compile_maths_expression_additive(self, expression):
if len(expression) > 2:
return self.compile_maths_expression(expression)
return self._compile_maths_expression(expression)
else:
op = {"+": ast.UAdd, "-": ast.USub}[expression.pop(0)]()
arg = expression.pop(0)
@ -1972,6 +1987,17 @@ class HyASTCompiler(object):
col_offset=arg.start_column)
return ret
@builds("&")
@builds_if("@", PY35)
@checkargs(min=1)
def compile_maths_expression_unary_idempotent(self, expression):
if len(expression) == 2:
# Used as a unary operator, this operator simply
# returns its argument.
return self.compile(expression[1])
else:
return self._compile_maths_expression(expression)
@builds("+")
def compile_maths_expression_add(self, expression):
if len(expression) == 1:
@ -1980,12 +2006,12 @@ class HyASTCompiler(object):
lineno=expression.start_line,
col_offset=expression.start_column)
else:
return self.compile_maths_expression_additive(expression)
return self._compile_maths_expression_additive(expression)
@builds("-")
@checkargs(min=1)
def compile_maths_expression_sub(self, expression):
return self.compile_maths_expression_additive(expression)
return self._compile_maths_expression_additive(expression)
@builds("+=")
@builds("/=")

View File

@ -22,72 +22,144 @@
;;;; Hy shadow functions
(import operator)
(import [hy._compat [PY35]])
(defn + [&rest args]
"Shadow + operator for when we need to import / map it against something"
(if
(= (len args) 0)
0
(= (len args) 1)
(operator.pos (get args 0))
args
(reduce operator.add args)
(raise (TypeError "Need at least 1 argument to add/concatenate"))))
(+ (first args))
; else
(reduce operator.add args)))
(defn - [&rest args]
(defn - [a1 &rest a-rest]
"Shadow - operator for when we need to import / map it against something"
(if
(= (len args) 1)
(- (get args 0))
args
(reduce operator.sub args)
(raise (TypeError "Need at least 1 argument to subtract"))))
(if a-rest
(reduce operator.sub a-rest a1)
(- a1)))
(defn * [&rest args]
"Shadow * operator for when we need to import / map it against something"
(if (= (len args) 0)
1 ; identity
(reduce operator.mul args)))
(defn / [&rest args]
"Shadow / operator for when we need to import / map it against something"
(if
(= (len args) 0)
1
(= (len args) 1)
(operator.truediv 1 (get args 0))
args
(reduce operator.truediv args)
(raise (TypeError "Need at least 1 argument to divide"))))
(first args)
; else
(reduce operator.mul args)))
(defn ** [a1 a2 &rest a-rest]
; We use `-foldr` instead of `reduce` because exponentiation
; is right-associative.
(-foldr operator.pow (+ (, a1 a2) a-rest)))
(defn -foldr [f xs]
(reduce (fn [x y] (f y x)) (cut xs None None -1)))
(defn comp-op [op args]
(defn / [a1 &rest a-rest]
"Shadow / operator for when we need to import / map it against something"
(if a-rest
(reduce operator.truediv a-rest a1)
(/ 1 a1)))
(defn // [a1 a2 &rest a-rest]
(reduce operator.floordiv (+ (, a2) a-rest) a1))
(defn % [x y]
(% x y))
(if PY35 (defn @ [a1 &rest a-rest]
(reduce operator.matmul a-rest a1)))
(defn << [a1 a2 &rest a-rest]
(reduce operator.lshift (+ (, a2) a-rest) a1))
(defn >> [a1 a2 &rest a-rest]
(reduce operator.rshift (+ (, a2) a-rest) a1))
(defn & [a1 &rest a-rest]
(if a-rest
(reduce operator.and_ a-rest a1)
a1))
(defn | [&rest args]
(if
(= (len args) 0)
0
(= (len args) 1)
(first args)
; else
(reduce operator.or_ args)))
(defn ^ [x y]
(^ x y))
(defn ~ [x]
(~ x))
(defn comp-op [op a1 a-rest]
"Helper for shadow comparison operators"
(if (< (len args) 2)
(raise (TypeError "Need at least 2 arguments to compare"))
(if a-rest
(reduce (fn [x y] (and x y))
(list-comp (op x y)
[(, x y) (zip args (cut args 1))]))))
(defn < [&rest args]
(list-comp (op x y) [(, x y) (zip (+ (, a1) a-rest) a-rest)]))
True))
(defn < [a1 &rest a-rest]
"Shadow < operator for when we need to import / map it against something"
(comp-op operator.lt args))
(defn <= [&rest args]
(comp-op operator.lt a1 a-rest))
(defn <= [a1 &rest a-rest]
"Shadow <= operator for when we need to import / map it against something"
(comp-op operator.le args))
(defn = [&rest args]
(comp-op operator.le a1 a-rest))
(defn = [a1 &rest a-rest]
"Shadow = operator for when we need to import / map it against something"
(comp-op operator.eq args))
(defn != [&rest args]
(comp-op operator.eq a1 a-rest))
(defn is [a1 &rest a-rest]
(comp-op operator.is_ a1 a-rest))
(defn != [a1 a2 &rest a-rest]
"Shadow != operator for when we need to import / map it against something"
(comp-op operator.ne args))
(defn >= [&rest args]
(comp-op operator.ne a1 (+ (, a2) a-rest)))
(defn is-not [a1 a2 &rest a-rest]
(comp-op operator.is-not a1 (+ (, a2) a-rest)))
(defn >= [a1 &rest a-rest]
"Shadow >= operator for when we need to import / map it against something"
(comp-op operator.ge args))
(defn > [&rest args]
(comp-op operator.ge a1 a-rest))
(defn > [a1 &rest a-rest]
"Shadow > operator for when we need to import / map it against something"
(comp-op operator.gt args))
(comp-op operator.gt a1 a-rest))
; TODO figure out a way to shadow "is", "is_not", "and", "or"
(defn and [&rest args]
(if
(= (len args) 0)
True
(= (len args) 1)
(first args)
; else
(reduce (fn [x y] (and x y)) args)))
(defn or [&rest args]
(if
(= (len args) 0)
None
(= (len args) 1)
(first args)
; else
(reduce (fn [x y] (or x y)) args)))
(setv *exports* ['+ '- '* '/ '< '<= '= '!= '>= '>])
(defn not [x]
(not x))
(defn in [x y]
(in x y))
(defn not-in [x y]
(not-in x y))
(setv *exports* [
'+ '- '* '** '/ '// '% '@
'<< '>> '& '| '^ '~
'< '> '<= '>= '= '!=
'and 'or 'not
'is 'is-not 'in 'not-in])
(if (not PY35)
(.remove *exports* '@))

View File

@ -12,7 +12,7 @@ from .native_tests.when import * # noqa
from .native_tests.with_decorator import * # noqa
from .native_tests.core import * # noqa
from .native_tests.reader_macros import * # noqa
from .native_tests.shadow import * # noqa
from .native_tests.operators import * # noqa
from .native_tests.with_test import * # noqa
from .native_tests.extra.anaphoric import * # noqa
from .native_tests.contrib.loop import * # noqa

View File

@ -498,7 +498,7 @@ def test_compile_error():
try:
can_compile("(fn [] (in [1 2 3]))")
except HyTypeError as e:
assert(e.message == "`in' needs at least 2 arguments, got 1.")
assert(e.message == "`in' needs 2 arguments, got 1")
else:
assert(False)

View File

@ -253,57 +253,6 @@
(assert (= fact 120)))
(defn test-not []
"NATIVE: test not"
(assert (not (= 1 2)))
(assert (= True (not False)))
(assert (= False (not 42))) )
(defn test-inv []
"NATIVE: test inv"
(assert (= (~ 1) -2))
(assert (= (~ -2) 1)))
(defn test-in []
"NATIVE: test in"
(assert (in "a" ["a" "b" "c" "d"]))
(assert (not-in "f" ["a" "b" "c" "d"])))
(defn test-noteq []
"NATIVE: not eq"
(assert (!= 2 3))
(assert (not (!= 1))))
(defn test-eq []
"NATIVE: eq"
(assert (= 1 1))
(assert (= 1)))
(defn test-numops []
"NATIVE: test numpos"
(assert (> 5 4 3 2 1))
(assert (> 1))
(assert (< 1 2 3 4 5))
(assert (< 1))
(assert (<= 5 5 5 5 ))
(assert (<= 1))
(assert (>= 5 5 5 5 ))
(assert (>= 1)))
(defn test-is []
"NATIVE: test is can deal with None"
(setv a None)
(assert (is a None))
(assert (is-not a "b"))
(assert (none? a)))
(defn test-branching []
"NATIVE: test if branching"
(if True

View File

@ -0,0 +1,286 @@
(import [hy._compat [PY35]])
(defmacro op-and-shadow-test [op &rest body]
; Creates two tests with the given `body`, one where all occurrences
; of the symbol `f` are syntactically replaced with `op` (a test of
; the real operator), and one where the body is preceded by an
; assignment of `op` to `f` (a test of the shadow operator).
;
; `op` can also be a list of operators, in which case two tests are
; created for each operator.
(import [hy [HySymbol HyString]] [hy.contrib.walk [prewalk]])
(setv defns [])
(for [o (if (coll? op) op [op])]
(.append defns `(defn ~(HySymbol (+ "test_operator_" o "_real")) []
(setv f-name ~(HyString o))
~@(prewalk :form body :f (fn [x]
(if (and (symbol? x) (= x "f")) o x)))))
(.append defns `(defn ~(HySymbol (+ "test_operator_" o "_shadow")) []
(setv f-name ~(HyString o))
(setv f ~o)
~@body)))
`(do ~@defns))
(defmacro forbid [expr]
`(assert (try
(eval '~expr)
(except [TypeError] True)
(else (raise AssertionError)))))
(op-and-shadow-test +
(assert (= (f) 0))
(defclass C [object] [__pos__ (fn [self] "called __pos__")])
(assert (= (f (C)) "called __pos__"))
(assert (= (f 1 2) 3))
(assert (= (f 1 2 3 4) 10))
(assert (= (f 1 2 3 4 5) 15))
; with strings
(assert (= (f "a" "b" "c")
"abc"))
; with lists
(assert (= (f ["a"] ["b"] ["c"])
["a" "b" "c"])))
(op-and-shadow-test -
(forbid (f))
(assert (= (f 1) -1))
(assert (= (f 2 1) 1))
(assert (= (f 2 1 1) 0)))
(op-and-shadow-test *
(assert (= (f) 1))
(assert (= (f 3) 3))
(assert (= (f 3 3) 9))
(assert (= (f 2 3 4) 24))
(assert (= (f "ke" 4) "kekekeke"))
(assert (= (f [1 2 3] 2) [1 2 3 1 2 3])))
(op-and-shadow-test **
(forbid (f))
(forbid (f 1))
(assert (= (f 3 2) 9))
(assert (= (f 5 4 3 2) (** 5 (** 4 (** 3 2))))))
; Exponentiation is right-associative.
(op-and-shadow-test /
(forbid (f))
(assert (= (f 2) .5))
(assert (= (f 3 2) 1.5))
(assert (= (f 8 2) 4))
(assert (= (f 8 2 2) 2))
(assert (= (f 8 2 2 2) 1)))
(op-and-shadow-test //
(forbid (f))
(forbid (f 1))
(assert (= (f 16 5) 3))
(assert (= (f 8 2) 4))
(assert (= (f 8 2 2) 2)))
(op-and-shadow-test %
(forbid (f))
(forbid (f 1))
(assert (= (f 16 5) 1))
(assert (= (f 8 2) 0))
(assert (= (f "aa %s bb" 15) "aa 15 bb"))
(assert (= (f "aa %s bb %s cc" (, "X" "Y")) "aa X bb Y cc"))
(forbid (f 1 2 3)))
(when PY35 (op-and-shadow-test @
(defclass C [object] [
__init__ (fn [self content] (setv self.content content))
__matmul__ (fn [self other] (C (+ self.content other.content)))])
(forbid (f))
(assert (do (setv c (C "a")) (is (f c) c)))
(assert (= (. (f (C "b") (C "c")) content) "bc"))
(assert (= (. (f (C "d") (C "e") (C "f")) content) "def"))))
(op-and-shadow-test <<
(forbid (f))
(forbid (f 1))
(assert (= (f 0b101 2) 0b10100))
(assert (= (f 0b101 2 3) 0b10100000)))
(op-and-shadow-test >>
(forbid (f))
(forbid (f 1))
(assert (= (f 0b101 2) 0b1))
(assert (= (f 0b101000010 2 3) 0b1010)))
(op-and-shadow-test &
(forbid (f))
; Binary AND has no identity element for the set of all
; nonnegative integers, because we can't have a 1 in every bit
; when there are infinitely many bits.
(assert (= (f 17) 17))
(assert (= (f 0b0011 0b0101) 0b0001))
(assert (= (f 0b111 0b110 0b100) 0b100)))
(op-and-shadow-test |
(assert (= (f) 0))
(assert (= (f 17) 17))
(assert (= (f 0b0011 0b0101) 0b0111))
(assert (= (f 0b11100 0b11000 0b10010) 0b11110)))
(op-and-shadow-test ^
(forbid (f))
(forbid (f 17))
(assert (= (f 0b0011 0b0101) 0b0110))
(forbid (f 0b111 0b110 0b100)))
; `xor` with 3 arguments is kill (https://github.com/hylang/hy/pull/1102),
; so we don't allow `^` with 3 arguments, either.
(op-and-shadow-test ~
(forbid (f))
(assert (= (f (chr 0b00101111)
(chr 0b11010000))))
(forbid (f (chr 0b00101111) (chr 0b11010000))))
(op-and-shadow-test <
(forbid (f))
(assert (is (f "hello") True))
(assert (is (f 1 2) True))
(assert (is (f 2 1) False))
(assert (is (f 1 1) False))
(assert (is (f 1 2 3) True))
(assert (is (f 3 2 1) False))
(assert (is (f 1 3 2) False))
(assert (is (f 1 2 2) False))
; Make sure chained comparisons use `and`, not `&`.
; https://github.com/hylang/hy/issues/1191
(defclass C [object] [
__init__ (fn [self x]
(setv self.x x))
__lt__ (fn [self other]
self.x)])
(assert (= (f (C "a") (C "b") (C "c")) "b")))
(op-and-shadow-test >
(forbid (f))
(assert (is (f "hello") True))
(assert (is (f 1 2) False))
(assert (is (f 2 1) True))
(assert (is (f 1 1) False))
(assert (is (f 1 2 3) False))
(assert (is (f 3 2 1) True))
(assert (is (f 1 3 2) False))
(assert (is (f 2 1 1) False)))
(op-and-shadow-test <=
(forbid (f))
(assert (is (f "hello") True))
(assert (is (f 1 2) True))
(assert (is (f 2 1) False))
(assert (is (f 1 1) True))
(assert (is (f 1 2 3) True))
(assert (is (f 3 2 1) False))
(assert (is (f 1 3 2) False))
(assert (is (f 1 2 2) True)))
(op-and-shadow-test >=
(forbid (f))
(assert (is (f "hello") True))
(assert (is (f 1 2) False))
(assert (is (f 2 1) True))
(assert (is (f 1 1) True))
(assert (is (f 1 2 3) False))
(assert (is (f 3 2 1) True))
(assert (is (f 1 3 2) False))
(assert (is (f 2 1 1) True)))
(op-and-shadow-test [= is]
(forbid (f))
(assert (is (f "hello") True))
(defclass C)
(setv x (get {"is" (C) "=" 0} f-name))
(setv y (get {"is" (C) "=" 1} f-name))
(assert (is (f x x) True))
(assert (is (f y y) True))
(assert (is (f x y) False))
(assert (is (f y x) False))
(assert (is (f x x x x x) True))
(assert (is (f x x x y x) False))
(setv n None)
(assert (is (f n None) True))
(assert (is (f n "b") False)))
(op-and-shadow-test [!= is-not]
(forbid (f))
(forbid (f "hello"))
(defclass C)
(setv x (get {"is_not" (C) "!=" 0} f-name))
(setv y (get {"is_not" (C) "!=" 1} f-name))
(setv z (get {"is_not" (C) "!=" 2} f-name))
(assert (is (f x x) False))
(assert (is (f y y) False))
(assert (is (f x y) True))
(assert (is (f y x) True))
(assert (is (f x y z) True))
(assert (is (f x x x) False))
(assert (is (f x y x) True))
(assert (is (f x x y) False)))
(op-and-shadow-test and
(assert (is (f) True))
(assert (= (f 17) 17))
(assert (= (f 1 2) 2))
(assert (= (f 1 0) 0))
(assert (= (f 0 2) 0))
(assert (= (f 0 0) 0))
(assert (= (f 1 2 3) 3))
(assert (= (f 1 0 3) 0))
(assert (= (f "a" 1 True [1]) [1])))
(op-and-shadow-test or
(assert (is (f) None))
(assert (= (f 17) 17))
(assert (= (f 1 2) 1))
(assert (= (f 1 0) 1))
(assert (= (f 0 2) 2))
(assert (= (f 0 0) 0))
(assert (= (f 1 2 3) 1))
(assert (= (f 0 0 3) 3))
(assert (= (f "" None 0 False []) [])))
(op-and-shadow-test not
(forbid (f))
(assert (is (f "hello") False))
(assert (is (f 0) True))
(assert (is (f None) True)))
(op-and-shadow-test [in not-in]
(forbid (f))
(forbid (f 3))
(assert (is (f 3 [1 2]) (!= f-name "in")))
(assert (is (f 2 [1 2]) (= f-name "in")))
(forbid (f 2 [1 2] [3 4])))

View File

@ -1,101 +0,0 @@
(defn test-shadow-addition []
"NATIVE: test shadow addition"
(setv x +)
(assert (try
(x)
(except [TypeError] True)
(else (raise AssertionError))))
(assert (= (x 1 2 3 4) 10))
(assert (= (x 1 2 3 4 5) 15))
; with strings
(assert (= (x "a" "b" "c")
"abc"))
; with lists
(assert (= (x ["a"] ["b"] ["c"])
["a" "b" "c"])))
(defn test-shadow-subtraction []
"NATIVE: test shadow subtraction"
(setv x -)
(assert (try
(x)
(except [TypeError] True)
(else (raise AssertionError))))
(assert (= (x 1) -1))
(assert (= (x 2 1) 1))
(assert (= (x 2 1 1) 0)))
(defn test-shadow-multiplication []
"NATIVE: test shadow multiplication"
(setv x *)
(assert (= (x) 1))
(assert (= (x 3) 3))
(assert (= (x 3 3) 9)))
(defn test-shadow-division []
"NATIVE: test shadow division"
(setv x /)
(assert (try
(x)
(except [TypeError] True)
(else (raise AssertionError))))
(assert (= (x 1) 1))
(assert (= (x 8 2) 4))
(assert (= (x 8 2 2) 2))
(assert (= (x 8 2 2 2) 1)))
(defn test-shadow-compare []
"NATIVE: test shadow compare"
(for [x [< <= = != >= >]]
(assert (try
(x)
(except [TypeError] True)
(else (raise AssertionError))))
(assert (try
(x 1)
(except [TypeError] True)
(else (raise AssertionError)))))
(for [(, x y) [[< >=]
[<= >]
[= !=]]]
(for [args [[1 2]
[2 1]
[1 1]
[2 2]]]
(assert (= (apply x args) (not (apply y args))))))
(setv s-lt <
s-gt >
s-le <=
s-ge >=
s-eq =
s-ne !=)
(assert (apply s-lt [1 2 3]))
(assert (not (apply s-lt [3 2 1])))
(assert (apply s-gt [3 2 1]))
(assert (not (apply s-gt [1 2 3])))
(assert (apply s-le [1 1 2 2 3 3]))
(assert (not (apply s-le [1 1 2 2 1 1])))
(assert (apply s-ge [3 3 2 2 1 1]))
(assert (not (apply s-ge [3 3 2 2 3 3])))
(assert (apply s-eq [1 1 1 1 1]))
(assert (not (apply s-eq [1 1 2 1 1])))
(assert (apply s-ne [1 2 3 4 5]))
(assert (not (apply s-ne [1 1 2 3 4])))
; Make sure chained comparisons use `and`, not `&`.
; https://github.com/hylang/hy/issues/1191
(defclass C [object] [
__init__ (fn [self x]
(setv self.x x))
__lt__ (fn [self other]
self.x)])
(assert (= (< (C "a") (C "b") (C "c")) "b"))
(setv f <)
(assert (= (f (C "a") (C "b") (C "c")) "b")))