Repository: hylang/hy

Issue:
better elif
Related to #713, and possibly #760. We should prefer Clojure forms over Common Lisp's. Can we leave out the extra brackets in `cond`? 

``` Lisp
; Common Lisp cond
(cond ((= x 42) "perfect")
      ((> x 42) "greater"))
      ("default"))
```

Clojure doesn't have them:

``` Clojure
; Clojure cond
(cond (= x 42) "perfect"
      (> x 42) "greater")
      :else "default")
```

```
; Hy
(cond [(= x 42) "perfect"]
      [(> x 42) "greater"]
      [True "default"])
```

I would rather use an explicit `do`/`progn` in the less common case that I need one, than have to type the extra brackets every single time.

This would, of course, break existing Hy code. If that's unacceptable, I have an alternative suggestion--extend the `if` form to accept more than three arguments:

``` Lisp
(if (= x 42) "perfect"
    (> x 42) "greater"
    "default")
```

Arc Lisp's `if` [actually works this way.](http://old.ycombinator.com/arc/tut.txt)
(tl;dr --- here's the relevant excerpts from the link above:)

```
An if with more than three arguments is equivalent to a nested if.  

(if a b c d e)

is equivalent to

(if a
    b
    (if c
        d
        e))

If you're used to languages with elseif, this pattern will be
familiar. [1]
```

```
Notes

[1] Note to Lisp hackers: If you're used to the conventional Lisp
cond operator, this if amounts to the same thing, but with fewer
parentheses.  E.g.

(cond (a b)
      (c d)
      (t e))

becomes

(if a b
    c d
      e)

JMC's original cond didn't have implicit progn, so the parens around
each pair of clauses were unnecessary.  They became necessary soon
after, however, when cond started to have implicit progn in the
first Lisp implementations.  This probably prevented people from
realizing they hadn't originally been needed.  But most conds in
the wild seem to occur in purely functional code, and thus pay the
cost in parens of implicit progn without actually needing it.  My
experience so far suggests it's a net win to offer progn a la carte
instead of combining it with the default conditional operator.
Having to use explicit dos may even be an advantage, because it
calls attention to nonfunctional code.
...

```



Discussion:

Resolving patch:
diff --git a/hy/compiler.py b/hy/compiler.py
--- a/hy/compiler.py
+++ b/hy/compiler.py
@@ -999,7 +999,7 @@ def _compile_catch_expression(self, expr, var):
             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 @@ def compile_if(self, expression):
         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


Test patch:
diff --git a/tests/compilers/test_ast.py b/tests/compilers/test_ast.py
--- a/tests/compilers/test_ast.py
+++ b/tests/compilers/test_ast.py
@@ -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))
     """)
diff --git a/tests/native_tests/language.hy b/tests/native_tests/language.hy
--- a/tests/native_tests/language.hy
+++ b/tests/native_tests/language.hy
@@ -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"))
diff --git a/tests/native_tests/native_macros.hy b/tests/native_tests/native_macros.hy
--- a/tests/native_tests/native_macros.hy
+++ b/tests/native_tests/native_macros.hy
@@ -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"