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
(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
below:
@ -365,9 +367,10 @@ below:
.. code-block:: clj
=> (defclass Cat []
... [[age None]
... [colour "white"]
... [speak (fn [self] (print "Meow"))]])
... [age None
... colour "white"]
...
... (defn speak [self] (print "Meow")))
=> (def spot (Cat))
=> (setv spot.colour "Black")

View File

@ -477,17 +477,14 @@ In Hy:
(defclass FooBar [object]
"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
(fn [self]
(defn --init-- [self x]
(setv self.x x)
None)
(defn get-x [self]
"Return our copy of x"
self.x)]])
self.x))
You can also do class-level attributes. In Python::
@ -502,9 +499,9 @@ In Hy:
.. code-block:: clj
(defclass Customer [models.Model]
[[name (models.CharField :max-length 255})]
[address (models.TextField)]
[notes (models.TextField)]])
[name (models.CharField :max-length 255})
address (models.TextField)
notes (models.TextField)])
Hy <-> Python interop
=====================

View File

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

View File

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

View File

@ -23,7 +23,7 @@
(defn test-defclass-attrs []
"NATIVE: test defclass attributes"
(defclass A []
[[x 42]])
[x 42])
(assert (= A.x 42))
(assert (= (getattr (A) "x") 42)))
@ -31,9 +31,9 @@
(defn test-defclass-attrs-fn []
"NATIVE: test defclass attributes with fn"
(defclass B []
[[x 42]
[y (fn [self value]
(+ self.x value))]])
[x 42
y (fn [self value]
(+ self.x value))])
(assert (= B.x 42))
(assert (= (.y (B) 5) 47))
(let [[b (B)]]
@ -44,17 +44,17 @@
(defn test-defclass-dynamic-inheritance []
"NATIVE: test defclass with dynamic inheritance"
(defclass A [((fn [] (if true list dict)))]
[[x 42]])
[x 42])
(assert (isinstance (A) list))
(defclass A [((fn [] (if false list dict)))]
[[x 42]])
[x 42])
(assert (isinstance (A) dict)))
(defn test-defclass-no-fn-leak []
"NATIVE: test defclass attributes with fn"
(defclass A []
[[x (fn [] 1)]])
[x (fn [] 1)])
(try
(do
(x)
@ -64,13 +64,13 @@
(defn test-defclass-docstring []
"NATIVE: test defclass docstring"
(defclass A []
[[--doc-- "doc string"]
[x 1]])
[--doc-- "doc string"
x 1])
(setv a (A))
(assert (= a.__doc__ "doc string"))
(defclass B []
"doc string"
[[x 1]])
[x 1])
(setv b (B))
(assert (= b.x 1))
(assert (= b.__doc__ "doc string"))
@ -78,7 +78,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]])
[x 1])
(setv mL (MultiLine))
(assert (= mL.x 1))
(assert (in "begin" mL.__doc__))
@ -86,8 +86,26 @@
(defn test-defclass-macroexpand []
"NATIVE: test defclass with macro expand"
(defmacro M [] `[x (fn [self x] (setv self._x x))])
(defclass A [] [(M)])
(defmacro M [] `(defn x [self x] (setv self._x x)))
(defclass A [] (M))
(setv a (A))
(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,7 +145,7 @@
(defclass HyTestMatrix [list]
[[--matmul--
[--matmul--
(fn [self other]
(let [[n (len self)]
[m (len (. other [0]))]
@ -159,7 +159,7 @@
(. other [k] [j]))))
(.append result-row dot-product)))
(.append result result-row)))
result))]])
result))])
(def first-test-matrix (HyTestMatrix [[1 2 3]
[4 5 6]

View File

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

View File

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