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:
gilch 2015-10-13 19:38:15 -06:00
parent 9a334c83bb
commit f4afb0ca7e
7 changed files with 100 additions and 37 deletions

View File

@ -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

View File

@ -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

View File

@ -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))

View File

@ -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]

View File

@ -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))
""")

View File

@ -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"))

View File

@ -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"