Repository: hylang/hy

Issue:
Allow ~@ splice on None
Related #896.

In most Lisps, `nil` is also the empty list. In Hy, `nil` used to be `None`. Python does not consider `None` to be iterable, but `unquote-splice` could have a special case for it. This would make macro writing easier. It's a common Lisp idiom to splice in an `if` expression during a macroexpansion which can optionally add some things or not. In Hy you have to put an empty iterable in the else part to make this work. If `None` were allowed, you could leave that off.

While we're at it, it would also be nice for macro writing if a `HyList` instance `+` `None` just returned the instance, but this is less consistent with Python lists.


Discussion:
By the way, Hy's `cons` already considers `None` to be the empty expression, so there's precedent for this kind of special casing. Compare:
```
=> (cons 1 None)
from hy.core.language import cons
cons(1, None)
(1)
=> (type (cons 1 None))
from hy.core.language import cons
type(cons(1, None))
<class 'hy.models.HyExpression'>
=> (cons 1 '())
from hy.core.language import cons
from hy import HyExpression
cons(1, HyExpression([]))
(1)
=> (type (cons 1 '()))
from hy.core.language import cons
from hy import HyExpression
type(cons(1, HyExpression([])))
<class 'hy.models.HyExpression'>
```
#1349 fixes this. The current `unquote-splice` receives the value of the form inside and passes it to Python's `list`. When the form inside `~` evaluates to `None`, it produces `list(None)`, but since `None` is not iterable, it results an error. Instead of passing the form to `list`, #1349 passes the form to a function that returns `[]` when it receives `None`, allowing `None` to be spliceable. 

Resolving patch:
diff --git a/hy/compiler.py b/hy/compiler.py
--- a/hy/compiler.py
+++ b/hy/compiler.py
@@ -707,7 +707,9 @@ def _render_quoted_form(self, form, level):
                                                                          level)
                 imports.update(f_imports)
                 if splice:
-                    to_add = HyExpression([HySymbol("list"), f_contents])
+                    to_add = HyExpression([
+                        HySymbol("list"),
+                        HyExpression([HySymbol("or"), f_contents, HyList()])])
                 else:
                     to_add = HyList([f_contents])
 


Test patch:
diff --git a/tests/native_tests/quote.hy b/tests/native_tests/quote.hy
--- a/tests/native_tests/quote.hy
+++ b/tests/native_tests/quote.hy
@@ -74,17 +74,17 @@
 (defn test-unquote-splice []
   "NATIVE: test splicing unquotes"
   (setv q (quote (c d e)))
-  (setv qq (quasiquote (a b (unquote-splice q) f (unquote-splice q))))
-  (assert (= (len qq) 9))
-  (assert (= qq (quote (a b c d e f c d e)))))
+  (setv qq `(a b ~@q f ~@q ~@0 ~@False ~@None g ~@(when False 1) h))
+  (assert (= (len qq) 11))
+  (assert (= qq (quote (a b c d e f c d e g h)))))
 
 
 (defn test-nested-quasiquote []
   "NATIVE: test nested quasiquotes"
-  (setv qq (quasiquote (1 (quasiquote (unquote (+ 1 (unquote (+ 2 3))))) 4)))
-  (setv q (quote (1 (quasiquote (unquote (+ 1 5))) 4)))
+  (setv qq `(1 `~(+ 1 ~(+ 2 3) ~@None) 4))
+  (setv q (quote (1 `~(+ 1 5) 4)))
   (assert (= (len q) 3))
-  (assert (= (get qq 1) (quote (quasiquote (unquote (+ 1 5))))))
+  (assert (= (get qq 1) (quote `~(+ 1 5))))
   (assert (= q qq)))