Repository: hylang/hy

Issue:
Remove redundant keywords
There are redundant keywords in the current implementation:
- `setf`/`setv`/`def`
- `lambda`/`fn`
- `for`/`foreach` (?)
- `throw`/`raise`
- `catch`/`except`
- `do`/`prgn`

I believe we should avoid any ambiguity by removing duplication and settling on one of each. It's one of these things that we should worry about now, while we can afford to break the language.



Discussion:
FWIW, `lambda` isn't entirely redundant, since it will attempt to use a python `lambda` if it's able to do so, and only create a `function` if it needs to, whereas `fn` always creates a `function`.

We should clean that up.

`def`, in my mind, should create a global symbol, a-la Clojure.

In retrospect, not such a great idea, since we do allow `(defn)` inside `(defn)`. More thought needed.

I added `for`/`foreach`, but I'm not sure. Is there anything that `for` can do that `foreach` cannot?

`for` can build nested loops neatly: `(for [x (range 10) y (range 10)] (print x y))`

I added 3 new pairs. I prefer `raise` and `except`, because of Python.

+1 for the comment on nested loops with `for`

We also have 
`defn`/`defun`
`defn-alias`/`defun-alias`
`first`/`car`
`lif`/`lisp-if` (and `-not`)
`rest`/`cdr`

This redundancy also bothers me. If there actually is a meaningful difference in how things are compiled/optimized, (`lambda`/`fn`? `def`/`setv`?) then it should at least be **documented**, so we know which one to use when. But even then I'd prefer not to have to choose. Hy should use Clojure forms rather than Common Lisp forms when there are near equivalents to choose between.

In cases the above doesn't apply, I generally prefer the shorter names.

@algernon Can we also have this one in the "Grand Language Cleanup" milestone?

The "which one to use" is a funny can of worms. But yes, this belongs to the Grand Language Cleanup.

Last time this came up, one idea was to have a namespace that sets up the aliases. So if someone prefers CL-ish names: `(import hy.style.common-lisp)`

> The "which one to use" is a funny can of worms.

_sigh_
I think Python3, then Clojure, then Common Lisp, in that order, is a good preference heuristic for which to use. In fact, I think it resolves all of them. Hy really does not need to be compatible with Common Lisp (or Clojure for that matter). Hy is its own Lisp dialect, and We're still Python(TM).

> Last time this came up, one idea was to have a namespace that sets up the aliases. 

Here's an idea for ya, `import hy.style` will not change the language, but instead print out friendly error messages whenever you attempt to use the wrong Lisp dialect. Like say you start a form with a `progn` symbol and Hy replies, "you meant to use a `do` here, didn't you, dear?" That way people will actually learn Hy, and will use a mutually intelligible version of it ;)

Haha, I like that suggestion.

Oh boy, the possibilities! Tie it in with hydiomatic, and funny warning messages.... mmm. That'd be going a bit too far, though. So perhaps `hy.style` shouldn't be part of the core language.

Found some more.
`True`/`true`
`False`/`false`
`None`/`nil`/`null`

I'll admit to using the easier-to-type aliases almost exclusively. But going by the heuristic, the clear winners are `True`/`False`/`None`. 

These have to be reserved words _anyway_ for Python interop, e.g. even if we decided to use `nil` exclusively to mean `None`, we still couldn't do `(setv None 42)`, because this isn't compatible with Python `None = 42  # error`, whereas the reverse, `nil = 42` is perfectly valid Python.

Common Lisp programmers unfamiliar with Clojure will be confused by `nil != []`. This will be less of an issue if we don't call a `None` a `nil`.

OK, we've got most of #880 merged (except the last commit, The `-alias` functions were moved instead of removed.). Now the following duplicates have been fixed:

```
raise / throw
except / catch
do / progn
defn / defun
lif / lisp-if and lif-not / lisp-if-not
defn-alias / defun-alias
```

The remaining duplicates (I know of) are:

```
True / true
False / false
None / nil / null
```

```
first / car
rest / cdr
lambda / fn
def / setv
```

I'd like to start with the first of the two remaining groups. In #880, @paultag said he's "OK with purging true false null and nil.", and my rationale is just above that. If another core member approves, I can try to put together a pull that purges these from Hy. If not, I'd rather not waste the time. It looks a little more difficult than the last fix.

I'm -1 for removing `true`, `false`, and `nil` because:
1. The argument that "users of other Lisps will be confused by the behavior of `nil`" doesn't matter, since one pretty much always learns Python _before_ Hy.
2. They look _really_ cool!

Now, I guess one of `None` aliases _could_ be removed, since maybe 3 is a bit much...

But `true` and `false` pretty much don't introduce any confusion, since it's the same word as the uppercase `True` and `False`.

One of `nil` and `null` should go, I agree, but not both. The lower-case names aren't confusing, and feel more Hyish to me. Purging them would create more work than what little complexity it removes. It's just not worth it.

So, for the second group, I suggest dropping `null`, that's neither Pythonic, nor Lispy.

I should just make a pull purging `null`, I doubt anyone would object, because it's hardly ever used. The others seem a bit more controversial, as anticipated.

Yeah, `null` isn't really used in Python or any Lisp: no objection there.


Resolving patch:
diff --git a/hy/compiler.py b/hy/compiler.py
--- a/hy/compiler.py
+++ b/hy/compiler.py
@@ -81,7 +81,7 @@ def load_stdlib():
 # keywords in Python 3.*
 def _is_hy_builtin(name, module_name):
     extras = ['True', 'False', 'None',
-              'true', 'false', 'nil', 'null']
+              'true', 'false', 'nil']
     if name in extras or keyword.iskeyword(name):
         return True
     # for non-Hy modules, check for pre-existing name in
diff --git a/hy/lex/parser.py b/hy/lex/parser.py
--- a/hy/lex/parser.py
+++ b/hy/lex/parser.py
@@ -289,7 +289,6 @@ def t_identifier(p):
         "true": "True",
         "false": "False",
         "nil": "None",
-        "null": "None",
     }
 
     if obj in table:


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
@@ -219,8 +219,8 @@ def test_ast_good_defclass():
 def test_ast_bad_defclass():
     "Make sure AST can't compile invalid defclass"
     cant_compile("(defclass)")
-    cant_compile("(defclass a null)")
-    cant_compile("(defclass a null null)")
+    cant_compile("(defclass a None)")
+    cant_compile("(defclass a None None)")
 
 
 def test_ast_good_lambda():
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
@@ -54,8 +54,6 @@
        (except [e [TypeError]] (assert (in "Can't assign to a builtin" (str e)))))
   (try (eval '(setv nil 1))
        (except [e [TypeError]] (assert (in "Can't assign to a builtin" (str e)))))
-  (try (eval '(setv null 1))
-       (except [e [TypeError]] (assert (in "Can't assign to a builtin" (str e)))))
   (try (eval '(defn defclass [] (print "hello")))
        (except [e [TypeError]] (assert (in "Can't assign to a builtin" (str e)))))
   (try (eval '(defn get [] (print "hello")))
@@ -228,7 +226,7 @@
   "NATIVE: test if cond sorta works."
   (cond
    [(= 1 2) (assert (is true false))]
-   [(is null null) (setv x true) (assert x)]))
+   [(is None None) (setv x true) (assert x)]))
 
 
 (defn test-index []
diff --git a/tests/native_tests/unless.hy b/tests/native_tests/unless.hy
--- a/tests/native_tests/unless.hy
+++ b/tests/native_tests/unless.hy
@@ -3,8 +3,8 @@
   (assert (= (unless false 1) 1))
   (assert (= (unless false 1 2) 2))
   (assert (= (unless false 1 3) 3))
-  (assert (= (unless true 2) null))
+  (assert (= (unless true 2) None))
   (assert (= (unless true 2) nil))
-  (assert (= (unless (!= 1 2) 42) null))
+  (assert (= (unless (!= 1 2) 42) None))
   (assert (= (unless (!= 1 2) 42) nil))
   (assert (= (unless (!= 2 2) 42) 42)))
diff --git a/tests/native_tests/when.hy b/tests/native_tests/when.hy
--- a/tests/native_tests/when.hy
+++ b/tests/native_tests/when.hy
@@ -3,8 +3,8 @@
   (assert (= (when true 1) 1))
   (assert (= (when true 1 2) 2))
   (assert (= (when true 1 3) 3))
-  (assert (= (when false 2) null))
-  (assert (= (when (= 1 2) 42) null))
+  (assert (= (when false 2) None))
+  (assert (= (when (= 1 2) 42) None))
   (assert (= (when false 2) nil))
   (assert (= (when (= 1 2) 42) nil))
   (assert (= (when (= 2 2) 42) 42)))