defclass reimagined

defclass now has a new syntax:

 (defclass Name [BaseList]
   [property value
    property value] ;; optional

   (defn method [self]
     self.property))

Anything after the optional property list (which will be translated to a
setv within the class context) will be added to the class body. This
allows one to have side effects and complex expressions within the class
definition.

As a side effect, defining methods is much more friendly now!

Closes #850.

Signed-off-by: Gergely Nagy <algernon@madhouse-project.org>
This commit is contained in:
Gergely Nagy 2015-08-04 16:43:07 +02:00
parent 9fbd21adba
commit cbc2eed900
No known key found for this signature in database
GPG Key ID: 0A083C5F06E0DD42
8 changed files with 99 additions and 91 deletions

View File

@ -357,7 +357,9 @@ attributes of the new class as two item vectors.
.. code-block:: clj .. code-block:: clj
(defclass class-name [super-class-1 super-class-2] (defclass class-name [super-class-1 super-class-2]
[[attribute value]]) [attribute value]
(defn method [self] (print "hello!")))
Both values and functions can be bound on the new class as shown by the example Both values and functions can be bound on the new class as shown by the example
below: below:
@ -365,9 +367,10 @@ below:
.. code-block:: clj .. code-block:: clj
=> (defclass Cat [] => (defclass Cat []
... [[age None] ... [age None
... [colour "white"] ... colour "white"]
... [speak (fn [self] (print "Meow"))]]) ...
... (defn speak [self] (print "Meow")))
=> (def spot (Cat)) => (def spot (Cat))
=> (setv spot.colour "Black") => (setv spot.colour "Black")

View File

@ -477,17 +477,14 @@ In Hy:
(defclass FooBar [object] (defclass FooBar [object]
"Yet Another Example Class" "Yet Another Example Class"
[[--init--
(fn [self x]
(setv self.x x)
; Currently needed for --init-- because __init__ needs None
; Hopefully this will go away :)
None)]
[get-x (defn --init-- [self x]
(fn [self] (setv self.x x)
"Return our copy of x" None)
self.x)]])
(defn get-x [self]
"Return our copy of x"
self.x))
You can also do class-level attributes. In Python:: You can also do class-level attributes. In Python::
@ -502,9 +499,9 @@ In Hy:
.. code-block:: clj .. code-block:: clj
(defclass Customer [models.Model] (defclass Customer [models.Model]
[[name (models.CharField :max-length 255})] [name (models.CharField :max-length 255})
[address (models.TextField)] address (models.TextField)
[notes (models.TextField)]]) notes (models.TextField)])
Hy <-> Python interop Hy <-> Python interop
===================== =====================

View File

@ -2238,15 +2238,15 @@ class HyASTCompiler(object):
@builds("defclass") @builds("defclass")
@checkargs(min=1) @checkargs(min=1)
def compile_class_expression(self, expression): def compile_class_expression(self, expressions):
expression.pop(0) # class expressions.pop(0) # class
class_name = expression.pop(0) class_name = expressions.pop(0)
if expression: if expressions:
base_list = expression.pop(0) base_list = expressions.pop(0)
if not isinstance(base_list, HyList): if not isinstance(base_list, HyList):
raise HyTypeError(expression, raise HyTypeError(expressions,
"Bases class must be a list") "Bases class must be a list")
bases_expr, bases, _ = self._compile_collect(base_list) bases_expr, bases, _ = self._compile_collect(base_list)
else: else:
@ -2256,8 +2256,8 @@ class HyASTCompiler(object):
body = Result() body = Result()
# grab the doc string, if there is one # grab the doc string, if there is one
if expression and isinstance(expression[0], HyString): if expressions and isinstance(expressions[0], HyString):
docstring = expression.pop(0) docstring = expressions.pop(0)
symb = HySymbol("__doc__") symb = HySymbol("__doc__")
symb.start_line = docstring.start_line symb.start_line = docstring.start_line
symb.start_column = docstring.start_column symb.start_column = docstring.start_column
@ -2266,31 +2266,25 @@ class HyASTCompiler(object):
docstring.start_column) docstring.start_column)
body += body.expr_as_stmt() body += body.expr_as_stmt()
if expression: if expressions and isinstance(expressions[0], HyList) \
try: and not isinstance(expressions[0], HyExpression):
body_expression = iter(expression.pop(0)) expr = expressions.pop(0)
except TypeError: body += self.compile(
raise HyTypeError( HyExpression([
expression, HySymbol("setv")
"Wrong argument type for defclass attributes definition.") ] + expr).replace(expr)
for b in body_expression: )
if isinstance(b, HyExpression):
b = macroexpand(b, self.module_name) for expression in expressions:
if len(b) != 2: body += self.compile(macroexpand(expression, self.module_name))
raise HyTypeError(
expression,
"Wrong number of argument in defclass attribute.")
body += self._compile_assign(b[0], b[1],
b.start_line, b.start_column)
body += body.expr_as_stmt()
if not body.stmts: if not body.stmts:
body += ast.Pass(lineno=expression.start_line, body += ast.Pass(lineno=expressions.start_line,
col_offset=expression.start_column) col_offset=expressions.start_column)
return bases + ast.ClassDef( return bases + ast.ClassDef(
lineno=expression.start_line, lineno=expressions.start_line,
col_offset=expression.start_column, col_offset=expressions.start_column,
decorator_list=[], decorator_list=[],
name=ast_str(class_name), name=ast_str(class_name),
keywords=[], keywords=[],

View File

@ -2,12 +2,11 @@
(defclass FakeMeth [] (defclass FakeMeth []
"Mocking decorator class" "Mocking decorator class"
[[rules {}] [rules {}]
[route (fn [self rule &kwargs options] (defn route [self rule &kwargs options]
(fn [f] (fn [f]
(assoc self.rules rule (, f options)) (assoc self.rules rule (, f options))
f))]]) f)))
(defn test_route [] (defn test_route []
(let [[app (FakeMeth)]] (let [[app (FakeMeth)]]

View File

@ -23,7 +23,7 @@
(defn test-defclass-attrs [] (defn test-defclass-attrs []
"NATIVE: test defclass attributes" "NATIVE: test defclass attributes"
(defclass A [] (defclass A []
[[x 42]]) [x 42])
(assert (= A.x 42)) (assert (= A.x 42))
(assert (= (getattr (A) "x") 42))) (assert (= (getattr (A) "x") 42)))
@ -31,9 +31,9 @@
(defn test-defclass-attrs-fn [] (defn test-defclass-attrs-fn []
"NATIVE: test defclass attributes with fn" "NATIVE: test defclass attributes with fn"
(defclass B [] (defclass B []
[[x 42] [x 42
[y (fn [self value] y (fn [self value]
(+ self.x value))]]) (+ self.x value))])
(assert (= B.x 42)) (assert (= B.x 42))
(assert (= (.y (B) 5) 47)) (assert (= (.y (B) 5) 47))
(let [[b (B)]] (let [[b (B)]]
@ -44,17 +44,17 @@
(defn test-defclass-dynamic-inheritance [] (defn test-defclass-dynamic-inheritance []
"NATIVE: test defclass with dynamic inheritance" "NATIVE: test defclass with dynamic inheritance"
(defclass A [((fn [] (if true list dict)))] (defclass A [((fn [] (if true list dict)))]
[[x 42]]) [x 42])
(assert (isinstance (A) list)) (assert (isinstance (A) list))
(defclass A [((fn [] (if false list dict)))] (defclass A [((fn [] (if false list dict)))]
[[x 42]]) [x 42])
(assert (isinstance (A) dict))) (assert (isinstance (A) dict)))
(defn test-defclass-no-fn-leak [] (defn test-defclass-no-fn-leak []
"NATIVE: test defclass attributes with fn" "NATIVE: test defclass attributes with fn"
(defclass A [] (defclass A []
[[x (fn [] 1)]]) [x (fn [] 1)])
(try (try
(do (do
(x) (x)
@ -64,13 +64,13 @@
(defn test-defclass-docstring [] (defn test-defclass-docstring []
"NATIVE: test defclass docstring" "NATIVE: test defclass docstring"
(defclass A [] (defclass A []
[[--doc-- "doc string"] [--doc-- "doc string"
[x 1]]) x 1])
(setv a (A)) (setv a (A))
(assert (= a.__doc__ "doc string")) (assert (= a.__doc__ "doc string"))
(defclass B [] (defclass B []
"doc string" "doc string"
[[x 1]]) [x 1])
(setv b (B)) (setv b (B))
(assert (= b.x 1)) (assert (= b.x 1))
(assert (= b.__doc__ "doc string")) (assert (= b.__doc__ "doc string"))
@ -78,7 +78,7 @@
"begin a very long multi-line string to make "begin a very long multi-line string to make
sure that it comes out the way we hope sure that it comes out the way we hope
and can span 3 lines end." and can span 3 lines end."
[[x 1]]) [x 1])
(setv mL (MultiLine)) (setv mL (MultiLine))
(assert (= mL.x 1)) (assert (= mL.x 1))
(assert (in "begin" mL.__doc__)) (assert (in "begin" mL.__doc__))
@ -86,8 +86,26 @@
(defn test-defclass-macroexpand [] (defn test-defclass-macroexpand []
"NATIVE: test defclass with macro expand" "NATIVE: test defclass with macro expand"
(defmacro M [] `[x (fn [self x] (setv self._x x))]) (defmacro M [] `(defn x [self x] (setv self._x x)))
(defclass A [] [(M)]) (defclass A [] (M))
(setv a (A)) (setv a (A))
(a.x 1) (a.x 1)
(assert (= a._x 1))) (assert (= a._x 1)))
(defn test-defclass-syntax []
"NATIVE: test defclass syntax with properties and methods and side-effects"
(setv foo 1)
(defclass A []
[x 1
y 2]
(global foo)
(setv foo 2)
(defn greet [self]
"Greet the caller"
"hello!"))
(setv a (A))
(assert (= a.x 1))
(assert (= a.y 2))
(assert foo 2)
(assert (.greet a) "hello"))

View File

@ -145,21 +145,21 @@
(defclass HyTestMatrix [list] (defclass HyTestMatrix [list]
[[--matmul-- [--matmul--
(fn [self other] (fn [self other]
(let [[n (len self)] (let [[n (len self)]
[m (len (. other [0]))] [m (len (. other [0]))]
[result []]] [result []]]
(for [i (range m)] (for [i (range m)]
(let [[result-row []]] (let [[result-row []]]
(for [j (range n)] (for [j (range n)]
(let [[dot-product 0]] (let [[dot-product 0]]
(for [k (range (len (. self [0])))] (for [k (range (len (. self [0])))]
(+= dot-product (* (. self [i] [k]) (+= dot-product (* (. self [i] [k])
(. other [k] [j])))) (. other [k] [j]))))
(.append result-row dot-product))) (.append result-row dot-product)))
(.append result result-row))) (.append result result-row)))
result))]]) result))])
(def first-test-matrix (HyTestMatrix [[1 2 3] (def first-test-matrix (HyTestMatrix [[1 2 3]
[4 5 6] [4 5 6]

View File

@ -13,7 +13,7 @@
(with-decorator bardec (with-decorator bardec
(defclass cls [] (defclass cls []
[[my_attr 456]])) [my_attr 456]))
(defn test-decorator-clobbing [] (defn test-decorator-clobbing []
"NATIVE: Tests whether nested decorators work" "NATIVE: Tests whether nested decorators work"

View File

@ -1,16 +1,13 @@
(defclass WithTest [object] (defclass WithTest [object]
[(--init-- (defn --init-- [self val]
(fn [self val] (setv self.val val)
(setv self.val val) None)
None))
(--enter-- (defn --enter-- [self]
(fn [self] self.val)
self.val))
(--exit-- (defn --exit-- [self type value traceback]
(fn [self type value traceback] (setv self.val None)))
(setv self.val None)))])
(defn test-single-with [] (defn test-single-with []
"NATIVE: test a single with" "NATIVE: test a single with"