Repository: hylang/hy

Issue:
Proposal: Remove the attribute list of `defclass`
Currently, `defclass` allows you to write

    (defclass C [Superclass]
      [foo 1  bar 2])

as shorthand for

    (defclass C [Superclass]
      (setv foo 1  bar 2))

But this need not be implemented in the compiler. If there was instead a special form called `defclass*` that didn't support attribute lists, you could get the same effect with

    (defmacro defclass [class-name superclasses &rest body]
      `(defclass* ~class-name ~superclasses
        ~@(if (and body (instance? HyList (first body)))
          [`(setv ~@(first body)) (cut body 1)]
          body)))

But the shorthand saves few characters, anyway, so perhaps we shouldn't even bother providing the macro.

What do you guys think?


Discussion:
I will generally agree that removing little niceties like that from the compiler&mdash;without them needing to be there&mdash;is good.
I'd be +1 for removing it tbh, it's kinda odd to read and not much of an improvement.

Resolving patch:
diff --git a/hy/compiler.py b/hy/compiler.py
--- a/hy/compiler.py
+++ b/hy/compiler.py
@@ -1475,10 +1475,9 @@ def compile_return(self, expr, root, arg):
 
     @special("defclass", [
         SYM,
-        maybe(brackets(many(FORM)) + maybe(STR) +
-              maybe(brackets(many(SYM + FORM))) + many(FORM))])
+        maybe(brackets(many(FORM)) + maybe(STR) + many(FORM))])
     def compile_class_expression(self, expr, root, name, rest):
-        base_list, docstring, attrs, body = rest or ([[]], None, None, [])
+        base_list, docstring, body = rest or ([[]], None, [])
 
         bases_expr, bases, keywords = (
             self._compile_collect(base_list[0], with_kwargs=True))
@@ -1488,11 +1487,6 @@ def compile_class_expression(self, expr, root, name, rest):
         if docstring is not None:
             bodyr += self.compile(docstring).expr_as_stmt()
 
-        if attrs is not None:
-            bodyr += self.compile(self._rewire_init(HyExpression(
-                [HySymbol("setv")] +
-                [x for pair in attrs[0] for x in pair]).replace(attrs)))
-
         for e in body:
             e = self.compile(self._rewire_init(
                 macroexpand(e, self.module, self)))


Test patch:
diff --git a/tests/native_tests/contrib/hy_repr.hy b/tests/native_tests/contrib/hy_repr.hy
--- a/tests/native_tests/contrib/hy_repr.hy
+++ b/tests/native_tests/contrib/hy_repr.hy
@@ -158,8 +158,8 @@
   (assert (= (hy-repr (C)) "cuddles"))
 
   (defclass Container [object]
-    [__init__ (fn [self value]
-      (setv self.value value))])
+    (defn __init__ [self value]
+      (setv self.value value)))
   (hy-repr-register Container :placeholder "(Container ...)" (fn [x]
     (+ "(Container " (hy-repr x.value) ")")))
   (setv container (Container 5))
@@ -170,5 +170,5 @@
 
 (defn test-hy-repr-fallback []
   (defclass D [object]
-    [__repr__ (fn [self] "cuddles")])
+    (defn __repr__ [self] "cuddles"))
   (assert (= (hy-repr (D)) "cuddles")))
diff --git a/tests/native_tests/core.hy b/tests/native_tests/core.hy
--- a/tests/native_tests/core.hy
+++ b/tests/native_tests/core.hy
@@ -300,7 +300,7 @@ result['y in globals'] = 'y' in globals()")
   (assert-requires-num inc)
 
   (defclass X [object]
-    [__add__ (fn [self other] (.format "__add__ got {}" other))])
+    (defn __add__ [self other] (.format "__add__ got {}" other)))
   (assert-equal (inc (X)) "__add__ got 1"))
 
 (defn test-instance []
diff --git a/tests/native_tests/defclass.hy b/tests/native_tests/defclass.hy
--- a/tests/native_tests/defclass.hy
+++ b/tests/native_tests/defclass.hy
@@ -27,7 +27,7 @@
 (defn test-defclass-attrs []
   "NATIVE: test defclass attributes"
   (defclass A []
-    [x 42])
+    (setv x 42))
   (assert (= A.x 42))
   (assert (= (getattr (A) "x")  42)))
 
@@ -35,9 +35,9 @@
 (defn test-defclass-attrs-fn []
   "NATIVE: test defclass attributes with fn"
   (defclass B []
-    [x 42
-     y (fn [self value]
-         (+ self.x value))])
+    (setv x 42)
+    (setv y (fn [self value]
+      (+ self.x value))))
   (assert (= B.x 42))
   (assert (= (.y (B) 5) 47))
   (setv b (B))
@@ -48,17 +48,17 @@
 (defn test-defclass-dynamic-inheritance []
   "NATIVE: test defclass with dynamic inheritance"
   (defclass A [((fn [] (if True list dict)))]
-    [x 42])
+    (setv x 42))
   (assert (isinstance (A) list))
   (defclass A [((fn [] (if False list dict)))]
-    [x 42])
+    (setv x 42))
   (assert (isinstance (A) dict)))
 
 
 (defn test-defclass-no-fn-leak []
   "NATIVE: test defclass attributes with fn"
   (defclass A []
-    [x (fn [] 1)])
+    (setv x (fn [] 1)))
   (try
    (do
     (x)
@@ -68,13 +68,13 @@
 (defn test-defclass-docstring []
   "NATIVE: test defclass docstring"
   (defclass A []
-    [--doc-- "doc string"
-     x 1])
+    (setv --doc-- "doc string")
+    (setv x 1))
   (setv a (A))
   (assert (= a.__doc__ "doc string"))
   (defclass B []
     "doc string"
-    [x 1])
+    (setv x 1))
   (setv b (B))
   (assert (= b.x 1))
   (assert (= b.__doc__ "doc string"))
@@ -82,7 +82,7 @@
     "begin a very long multi-line string to make
      sure that it comes out the way we hope
      and can span 3 lines end."
-    [x 1])
+    (setv x 1))
   (setv mL (MultiLine))
   (assert (= mL.x 1))
   (assert (in "begin" mL.__doc__))
@@ -100,8 +100,8 @@
   "NATIVE: test defclass syntax with properties and methods and side-effects"
   (setv foo 1)
   (defclass A []
-    [x 1
-     y 2]
+    (setv x 1)
+    (setv y 2)
     (global foo)
     (setv foo 2)
     (defn greet [self]
@@ -117,7 +117,7 @@
 (defn test-defclass-implicit-none-for-init []
   "NATIVE: test that defclass adds an implicit None to --init--"
   (defclass A []
-    [--init-- (fn [self] (setv self.x 1) 42)])
+    (setv --init-- (fn [self] (setv self.x 1) 42)))
   (defclass B []
     (defn --init-- [self]
       (setv self.x 2)
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
@@ -129,8 +129,8 @@
   (assert (none? (setv (get {} "x") 42)))
   (setv l [])
   (defclass Foo [object]
-    [__setattr__ (fn [self attr val]
-      (.append l [attr val]))])
+    (defn __setattr__ [self attr val]
+      (.append l [attr val])))
   (setv x (Foo))
   (assert (none? (setv x.eggs "ham")))
   (assert (not (hasattr x "eggs")))
@@ -443,9 +443,9 @@
 
   (defclass X [object] [])
   (defclass M [object]
-    [meth (fn [self &rest args &kwargs kwargs]
+    (defn meth [self &rest args &kwargs kwargs]
       (.join " " (+ (, "meth") args
-        (tuple (map (fn [k] (get kwargs k)) (sorted (.keys kwargs)))))))])
+        (tuple (map (fn [k] (get kwargs k)) (sorted (.keys kwargs))))))))
 
   (setv x (X))
   (setv m (M))
@@ -1667,7 +1667,7 @@ macros()
 (defn test-underscore_variables []
   ; https://github.com/hylang/hy/issues/1340
   (defclass XYZ []
-    [_42 6])
+    (setv _42 6))
   (setv x (XYZ))
   (assert (= (. x _42) 6)))
 
@@ -1773,24 +1773,26 @@ macros()
 
 (defn test-pep-3115 []
   (defclass member-table [dict]
-    [--init-- (fn [self] (setv self.member-names []))
+    (defn --init-- [self]
+      (setv self.member-names []))
 
-     --setitem-- (fn [self key value]
-                   (if (not-in key self)
-                       (.append self.member-names key))
-                   (dict.--setitem-- self key value))])
+    (defn --setitem-- [self key value]
+      (if (not-in key self)
+          (.append self.member-names key))
+      (dict.--setitem-- self key value)))
 
   (defclass OrderedClass [type]
-    [--prepare-- (classmethod (fn [metacls name bases] (member-table)))
+    (setv --prepare-- (classmethod (fn [metacls name bases]
+      (member-table))))
 
-     --new-- (fn [cls name bases classdict]
-               (setv result (type.--new-- cls name bases (dict classdict)))
-               (setv result.member-names classdict.member-names)
-               result)])
+    (defn --new-- [cls name bases classdict]
+      (setv result (type.--new-- cls name bases (dict classdict)))
+      (setv result.member-names classdict.member-names)
+      result))
 
   (defclass MyClass [:metaclass OrderedClass]
-    [method1 (fn [self] (pass))
-     method2 (fn [self] (pass))])
+    (defn method1 [self] (pass))
+    (defn method2 [self] (pass)))
 
   (assert (= (. (MyClass) member-names)
              ["__module__" "__qualname__" "method1" "method2"])))
diff --git a/tests/native_tests/mathematics.hy b/tests/native_tests/mathematics.hy
--- a/tests/native_tests/mathematics.hy
+++ b/tests/native_tests/mathematics.hy
@@ -34,7 +34,7 @@
   "NATIVE: test that unary + calls __pos__"
 
   (defclass X [object]
-    [__pos__ (fn [self] "called __pos__")])
+    (defn __pos__ [self] "called __pos__"))
   (assert (= (+ (X)) "called __pos__"))
 
   ; Make sure the shadowed version works, too.
@@ -159,21 +159,20 @@
 
 
 (defclass HyTestMatrix [list]
-  [--matmul--
-   (fn [self other]
-     (setv n (len self)
-           m (len (. other [0]))
-           result [])
-     (for [i (range m)]
-       (setv result-row [])
-       (for [j (range n)]
-         (setv dot-product 0)
-         (for [k (range (len (. self [0])))]
-           (+= dot-product (* (. self [i] [k])
-                              (. other [k] [j]))))
-         (.append result-row dot-product))
-       (.append result result-row))
-     result)])
+  (defn --matmul-- [self other]
+    (setv n (len self)
+          m (len (. other [0]))
+          result [])
+    (for [i (range m)]
+      (setv result-row [])
+      (for [j (range n)]
+        (setv dot-product 0)
+        (for [k (range (len (. self [0])))]
+          (+= dot-product (* (. self [i] [k])
+                             (. other [k] [j]))))
+        (.append result-row dot-product))
+      (.append result result-row))
+    result))
 
 (setv first-test-matrix (HyTestMatrix [[1 2 3]
                                        [4 5 6]
diff --git a/tests/native_tests/operators.hy b/tests/native_tests/operators.hy
--- a/tests/native_tests/operators.hy
+++ b/tests/native_tests/operators.hy
@@ -34,7 +34,7 @@
 
   (assert (= (f) 0))
 
-  (defclass C [object] [__pos__ (fn [self] "called __pos__")])
+  (defclass C [object] (defn __pos__ [self] "called __pos__"))
   (assert (= (f (C)) "called __pos__"))
 
   (assert (= (f 1 2) 3))
@@ -101,9 +101,9 @@
 
 
 (op-and-shadow-test @
-  (defclass C [object] [
-    __init__ (fn [self content] (setv self.content content))
-    __matmul__ (fn [self other] (C (+ self.content other.content)))])
+  (defclass C [object]
+    (defn __init__ [self content] (setv self.content content))
+    (defn __matmul__ [self other] (C (+ self.content other.content))))
   (forbid (f))
   (assert (do (setv c (C "a")) (is (f c) c)))
   (assert (= (. (f (C "b") (C "c")) content) "bc"))
@@ -171,11 +171,11 @@
 
   ; Make sure chained comparisons use `and`, not `&`.
   ; https://github.com/hylang/hy/issues/1191
-  (defclass C [object] [
-    __init__ (fn [self x]
+  (defclass C [object]
+    (defn __init__ [self x]
       (setv self.x x))
-    __lt__ (fn [self other]
-      self.x)])
+    (defn __lt__ [self other]
+      self.x))
   (assert (= (f (C "a") (C "b") (C "c")) "b")))
 
 
diff --git a/tests/native_tests/py36_only_tests.hy b/tests/native_tests/py36_only_tests.hy
--- a/tests/native_tests/py36_only_tests.hy
+++ b/tests/native_tests/py36_only_tests.hy
@@ -40,8 +40,8 @@
 
 (defn test-pep-487 []
   (defclass QuestBase []
-    [--init-subclass-- (fn [cls swallow &kwargs kwargs]
-                         (setv cls.swallow swallow))])
+    (defn --init-subclass-- [cls swallow &kwargs kwargs]
+      (setv cls.swallow swallow)))
 
   (defclass Quest [QuestBase :swallow "african"])
   (assert (= (. (Quest) swallow) "african")))
diff --git a/tests/native_tests/with_decorator.hy b/tests/native_tests/with_decorator.hy
--- a/tests/native_tests/with_decorator.hy
+++ b/tests/native_tests/with_decorator.hy
@@ -27,7 +27,7 @@
     cls)
   (with-decorator bardec
     (defclass cls []
-      [attr1 123]))
+      (setv attr1 123)))
   (assert (= cls.attr1 123))
   (assert (= cls.attr2 456)))
 
diff --git a/tests/resources/pydemo.hy b/tests/resources/pydemo.hy
--- a/tests/resources/pydemo.hy
+++ b/tests/resources/pydemo.hy
@@ -142,13 +142,13 @@ Call me Ishmael. Some years ago—never mind how long precisely—having little
 
 (defclass C2 [C1]
   "class docstring"
-  [attr1 5  attr2 6]
-  (setv attr3 7))
+  (setv attr1 5)
+  (setv attr2 6))
 
 (import [contextlib [closing]])
 (setv closed [])
 (defclass Closeable []
-  [close (fn [self] (.append closed self.x))])
+  (defn close [self] (.append closed self.x)))
 (with [c1 (closing (Closeable)) c2 (closing (Closeable))]
   (setv c1.x "v1")
   (setv c2.x "v2"))
diff --git a/tests/test_hy2py.py b/tests/test_hy2py.py
--- a/tests/test_hy2py.py
+++ b/tests/test_hy2py.py
@@ -113,7 +113,7 @@ class C: pass
 
     assert m.C2.__doc__ == "class docstring"
     assert issubclass(m.C2, m.C1)
-    assert (m.C2.attr1, m.C2.attr2, m.C2.attr3) == (5, 6, 7)
+    assert (m.C2.attr1, m.C2.attr2) == (5, 6)
 
     assert m.closed == ["v2", "v1"]