variadic if
The `if` form now supports elif clauses. It works like `cond` but without the implicit `do`. The old `if` is now `if*` variadic lif now supports "ellif" clauses. Update if-no-waste compiler to use `if*` properly. (Sometimes one character is all it takes.) document if reword truthiness
This commit is contained in:
parent
9a334c83bb
commit
f4afb0ca7e
@ -823,26 +823,47 @@ keyword, the second function would have raised a ``NameError``.
|
||||
(set-a 5)
|
||||
(print-a)
|
||||
|
||||
if / if-not
|
||||
-----------
|
||||
if / if* / if-not
|
||||
-----------------
|
||||
|
||||
.. versionadded:: 0.10.0
|
||||
if-not
|
||||
|
||||
``if`` is used to conditionally select code to be executed. It has to contain a
|
||||
condition block and the block to be executed if the condition block evaluates
|
||||
to ``True``. Optionally, it may contain a final block that is executed in case
|
||||
the evaluation of the condition is ``False``.
|
||||
``if / if* / if-not`` respect Python *truthiness*, that is, a *test* fails if it
|
||||
evaluates to a "zero" (including values of ``len`` zero, ``nil``, and
|
||||
``false``), and passes otherwise, but values with a ``__bool__`` method
|
||||
(``__nonzero__`` in Python 2) can overrides this.
|
||||
|
||||
``if-not`` is similar, but the second block will be executed when the condition
|
||||
fails while the third and final block is executed when the test succeeds -- the
|
||||
opposite order of ``if``.
|
||||
The ``if`` macro is for conditionally selecting an expression for evaluation.
|
||||
The result of the selected expression becomes the result of the entire ``if``
|
||||
form. ``if`` can select a group of expressions with the help of a ``do`` block.
|
||||
|
||||
``if`` takes any number of alternating *test* and *then* expressions, plus an
|
||||
optional *else* expression at the end, which defaults to ``nil``. ``if`` checks
|
||||
each *test* in turn, and selects the *then* corresponding to the first passed
|
||||
test. ``if`` does not evaluate any expressions following its selection, similar
|
||||
to the ``if/elif/else`` control structure from Python. If no tests pass, ``if``
|
||||
selects *else*.
|
||||
|
||||
The ``if*`` special form is restricted to 2 or 3 arguments, but otherwise works
|
||||
exactly like ``if`` (which expands to nested ``if*`` forms), so there is
|
||||
generally no reason to use it directly.
|
||||
|
||||
``if-not`` is similar to ``if*`` but the second expression will be executed
|
||||
when the condition fails while the third and final expression is executed when
|
||||
the test succeeds -- the opposite order of ``if*``. The final expression is
|
||||
again optional and defaults to ``nil``.
|
||||
|
||||
Example usage:
|
||||
|
||||
.. code-block:: clj
|
||||
|
||||
(if (money-left? account)
|
||||
(print (if (< n 0.0) "negative"
|
||||
(= n 0.0) "zero"
|
||||
(> n 0.0) "positive"
|
||||
"not a number"))
|
||||
|
||||
(if* (money-left? account)
|
||||
(print "let's go shopping")
|
||||
(print "let's go and work"))
|
||||
|
||||
@ -850,9 +871,6 @@ Example usage:
|
||||
(print "let's go and work")
|
||||
(print "let's go shopping"))
|
||||
|
||||
Python truthiness is respected. ``None``, ``False``, zero of any numeric type,
|
||||
an empty sequence, and an empty dictionary are considered ``False``; everything
|
||||
else is considered ``True``.
|
||||
|
||||
|
||||
lif and lif-not
|
||||
|
@ -999,7 +999,7 @@ class HyASTCompiler(object):
|
||||
name=name,
|
||||
body=body)
|
||||
|
||||
@builds("if")
|
||||
@builds("if*")
|
||||
@checkargs(min=2, max=3)
|
||||
def compile_if(self, expression):
|
||||
expression.pop(0)
|
||||
@ -1011,7 +1011,7 @@ class HyASTCompiler(object):
|
||||
if expression:
|
||||
orel_expr = expression.pop(0)
|
||||
if isinstance(orel_expr, HyExpression) and isinstance(orel_expr[0],
|
||||
HySymbol) and orel_expr[0] == 'if':
|
||||
HySymbol) and orel_expr[0] == 'if*':
|
||||
# Nested ifs: don't waste temporaries
|
||||
root = self.temp_if is None
|
||||
nested = True
|
||||
|
@ -25,12 +25,20 @@
|
||||
;;; These macros are the essential hy macros.
|
||||
;;; They are automatically required everywhere, even inside hy.core modules.
|
||||
|
||||
(defmacro if [&rest args]
|
||||
"if with elif"
|
||||
(setv n (len args))
|
||||
(if* n
|
||||
(if* (= n 1)
|
||||
(get args 0)
|
||||
`(if* ~(get args 0)
|
||||
~(get args 1)
|
||||
(if ~@(cut args 2))))))
|
||||
|
||||
(defmacro macro-error [location reason]
|
||||
"error out properly within a macro"
|
||||
`(raise (hy.errors.HyMacroExpansionError ~location ~reason)))
|
||||
|
||||
|
||||
(defmacro defn [name lambda-list &rest body]
|
||||
"define a function `name` with signature `lambda-list` and body `body`"
|
||||
(if (not (= (type name) HySymbol))
|
||||
@ -39,7 +47,6 @@
|
||||
(macro-error name "defn takes a parameter list as second argument"))
|
||||
`(setv ~name (fn ~lambda-list ~@body)))
|
||||
|
||||
|
||||
(defmacro let [variables &rest body]
|
||||
"Execute `body` in the lexical context of `variables`"
|
||||
(if (not (isinstance variables HyList))
|
||||
|
@ -159,20 +159,25 @@
|
||||
ret)
|
||||
|
||||
|
||||
(defmacro if-not [test not-branch &optional [yes-branch nil]]
|
||||
(defmacro if-not [test not-branch &optional yes-branch]
|
||||
"Like `if`, but execute the first branch when the test fails"
|
||||
(if (nil? yes-branch)
|
||||
`(if (not ~test) ~not-branch)
|
||||
`(if (not ~test) ~not-branch ~yes-branch)))
|
||||
`(if* (not ~test) ~not-branch ~yes-branch))
|
||||
|
||||
|
||||
(defmacro lif [test &rest branches]
|
||||
(defmacro lif [&rest args]
|
||||
"Like `if`, but anything that is not None/nil is considered true."
|
||||
`(if (is-not ~test nil) ~@branches))
|
||||
(setv n (len args))
|
||||
(if* n
|
||||
(if* (= n 1)
|
||||
(get args 0)
|
||||
`(if* (is-not ~(get args 0) nil)
|
||||
~(get args 1)
|
||||
(lif ~@(cut args 2))))))
|
||||
|
||||
(defmacro lif-not [test &rest branches]
|
||||
|
||||
(defmacro lif-not [test not-branch &optional yes-branch]
|
||||
"Like `if-not`, but anything that is not None/nil is considered true."
|
||||
`(if (is ~test nil) ~@branches))
|
||||
`(if* (is ~test nil) ~not-branch ~yes-branch))
|
||||
|
||||
|
||||
(defmacro when [test &rest body]
|
||||
|
@ -73,15 +73,15 @@ def test_ast_bad_type():
|
||||
|
||||
|
||||
def test_ast_bad_if():
|
||||
"Make sure AST can't compile invalid if"
|
||||
cant_compile("(if)")
|
||||
cant_compile("(if foobar)")
|
||||
cant_compile("(if 1 2 3 4 5)")
|
||||
"Make sure AST can't compile invalid if*"
|
||||
cant_compile("(if*)")
|
||||
cant_compile("(if* foobar)")
|
||||
cant_compile("(if* 1 2 3 4 5)")
|
||||
|
||||
|
||||
def test_ast_valid_if():
|
||||
"Make sure AST can't compile invalid if"
|
||||
can_compile("(if foo bar)")
|
||||
"Make sure AST can compile valid if*"
|
||||
can_compile("(if* foo bar)")
|
||||
|
||||
|
||||
def test_ast_valid_unary_op():
|
||||
@ -539,13 +539,13 @@ def test_invalid_list_comprehension():
|
||||
|
||||
def test_bad_setv():
|
||||
"""Ensure setv handles error cases"""
|
||||
cant_compile("(setv if 1)")
|
||||
cant_compile("(setv if* 1)")
|
||||
cant_compile("(setv (a b) [1 2])")
|
||||
|
||||
|
||||
def test_defn():
|
||||
"""Ensure that defn works correctly in various corner cases"""
|
||||
cant_compile("(defn if [] 1)")
|
||||
cant_compile("(defn if* [] 1)")
|
||||
cant_compile("(defn \"hy\" [] 1)")
|
||||
cant_compile("(defn :hy [] 1)")
|
||||
can_compile("(defn &hy [] 1)")
|
||||
@ -561,5 +561,5 @@ def test_setv_builtins():
|
||||
(defn get [self] 42)
|
||||
(defclass B []
|
||||
(defn get [self] 42))
|
||||
(defn if [self] 0))
|
||||
(defn if* [self] 0))
|
||||
""")
|
||||
|
@ -274,6 +274,32 @@
|
||||
(assert (= (cond) nil)))
|
||||
|
||||
|
||||
(defn test-if []
|
||||
"NATIVE: test if if works."
|
||||
;; with an odd number of args, the last argument is the default case
|
||||
(assert (= 1 (if 1)))
|
||||
(assert (= 1 (if 0 -1
|
||||
1)))
|
||||
;; with an even number of args, the default is nil
|
||||
(assert (is nil (if)))
|
||||
(assert (is nil (if 0 1)))
|
||||
;; test deeper nesting
|
||||
(assert (= 42
|
||||
(if 0 0
|
||||
nil 1
|
||||
"" 2
|
||||
1 42
|
||||
1 43)))
|
||||
;; test shortcutting
|
||||
(setv x nil)
|
||||
(if 0 (setv x 0)
|
||||
"" (setv x "")
|
||||
42 (setv x 42)
|
||||
43 (setv x 43)
|
||||
(setv x "default"))
|
||||
(assert (= x 42)))
|
||||
|
||||
|
||||
(defn test-index []
|
||||
"NATIVE: Test that dict access works"
|
||||
(assert (= (get {"one" "two"} "one") "two"))
|
||||
|
@ -190,11 +190,11 @@
|
||||
|
||||
(defn test-lif []
|
||||
"test that lif works as expected"
|
||||
; nil is false
|
||||
;; nil is false
|
||||
(assert (= (lif None "true" "false") "false"))
|
||||
(assert (= (lif nil "true" "false") "false"))
|
||||
|
||||
; But everything else is True! Even falsey things.
|
||||
;; But everything else is True! Even falsey things.
|
||||
(assert (= (lif True "true" "false") "true"))
|
||||
(assert (= (lif False "true" "false") "true"))
|
||||
(assert (= (lif 0 "true" "false") "true"))
|
||||
@ -202,7 +202,14 @@
|
||||
(assert (= (lif "" "true" "false") "true"))
|
||||
(assert (= (lif (+ 1 2 3) "true" "false") "true"))
|
||||
(assert (= (lif nil "true" "false") "false"))
|
||||
(assert (= (lif 0 "true" "false") "true")))
|
||||
(assert (= (lif 0 "true" "false") "true"))
|
||||
|
||||
;; Test ellif [sic]
|
||||
(assert (= (lif nil 0
|
||||
nil 1
|
||||
0 2
|
||||
3)
|
||||
2)))
|
||||
|
||||
(defn test-lif-not []
|
||||
"test that lif-not works as expected"
|
||||
|
Loading…
Reference in New Issue
Block a user