Repository: hylang/hy

Issue:
What should `()` mean?
Currently, `()` produces an empty list, so it's equivalent to `[]`. It would perhaps be less surprising, and more consistent with Python, if it produced an empty tuple instead. On the other hand, Hy makes a point of treating its tuple construct, `(, …)`, as an ordinary special form, with no idiosyncratic syntax. One is clearly expected to write the empty tuple as `(,)`. We could disallow `()` entirely if we were feeling paranoid.


Discussion:
Two ways to see this:

- `()` in many functional languages is a unit value, so it would make sense by this logic to make it the empty tuple. (Based on the unit definition, it would actually be `None`, but that makes literally no sense here...)
- On the other hand, this *is* a Lisp, where `()` signifies expressions, but the concept of an empty expression doesn't really make sense. Therefore, it should be disallowed.

In all honesty, based on the latter definition (an "empty expression"), I'm REALLY tempted to say that it should be a `pass`-like item, but that (again) wouldn't really make much sense.

All that to say: it should probably be disallowed.
We can already make an empty expression with `'()`, an empty tuple with `(,)`, and an empty list with `[]`. If I had to pick one for `()` to be, it would be `'()`. But I agree that `[]` makes even less sense than `(,)`.

In some Lisps it's synonymous with `nil`. #896. But not Clojure, where those are distinct.

I think that Hy's special forms that return a default `None` should be returning some kind of empty/falsey type instead of just a falsey one.
I'm also leaning towards disallowed for the moment. If we do find a good use for it later, it will be easier to add it back if it's not allowed now.
As a person recently introduced to Hy (but familiar with Lisps), I found it surprising/confusing.

Resolving patch:
diff --git a/hy/compiler.py b/hy/compiler.py
--- a/hy/compiler.py
+++ b/hy/compiler.py
@@ -1546,8 +1546,9 @@ def compile_expression(self, expression):
             # Go through compile again if the type changed.
             return self.compile(expression)
 
-        if expression == []:
-            return self.compile_atom(HyList().replace(expression))
+        if not expression:
+            raise HyTypeError(
+                expression, "empty expressions are not allowed at top level")
 
         fn = expression[0]
         func = None


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
@@ -67,6 +67,12 @@ class C:
         pass
 
 
+def test_empty_expr():
+    "Empty expressions should be illegal at the top level."
+    cant_compile("(print ())")
+    can_compile("(print '())")
+
+
 def test_ast_bad_if():
     "Make sure AST can't compile invalid if*"
     cant_compile("(if*)")
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
@@ -1401,11 +1401,6 @@
   (assert (= y [5])))
 
 
-(defn test-empty-list []
-  "Evaluate an empty list to a []"
-  (assert (= () [])))
-
-
 (defn test-string []
   (assert (string? (string "a")))
   (assert (string? (string 1)))