reader macro #@ for with-decorator

The `with-decorator` special form is not the most ergonomic—this commit
introduces a new builtin `#@` reader macro that expands to an invocation
of `with-decorator`. To support this, `reader_macroexpand` is made to
also look in the default `None` namespace, in imitation of how
regular (non-reader) macros defined in hy.core are looked up. The
docstring of `hy.macros.reader` is also edited slightly for accuracy.

This in the matter of issue #856.
This commit is contained in:
Zack M. Davis 2015-07-26 14:19:10 -07:00
parent f247056360
commit 016557deab
4 changed files with 70 additions and 10 deletions

View File

@ -1463,6 +1463,25 @@ will be 4 (``1+1 + 1+1``).
8
#@
~~
.. versionadded:: 0.12.0
The :ref:`reader macro<reader-macros>` ``#@`` can be used as a shorthand
for ``with-decorator``. With ``#@``, the previous example becomes:
.. code-block:: clj
=> #@(inc-decorator (defn addition [a b] (+ a b)))
=> (addition 1 1)
4
=> #@(inc2-decorator inc-decorator
... (defn addition [a b] (+ a b)))
=> (addition 1 1)
8
.. _with-gensyms:
with-gensyms

View File

@ -231,3 +231,9 @@
(repeat r.text)))
(catch [e ImportError]
(repeat "Botsbuildbots requires `requests' to function."))))
(defreader @ [expr]
(let [[decorators (slice expr nil -1)]
[fndef (get expr -1)]]
`(with-decorator ~@decorators ~fndef)))

View File

@ -60,7 +60,7 @@ def macro(name):
def reader(name):
"""Decorator to define a macro called `name`.
"""Decorator to define a reader macro called `name`.
This stores the macro `name` in the namespace for the module where it is
defined.
@ -68,7 +68,7 @@ def reader(name):
If the module where it is defined is in `hy.core`, then the macro is stored
in the default `None` namespace.
This function is called from the `defmacro` special form in the compiler.
This function is called from the `defreader` special form in the compiler.
"""
def _(fn):
@ -176,14 +176,15 @@ def reader_macroexpand(char, tree, module_name):
"""Expand the reader macro "char" with argument `tree`."""
load_macros(module_name)
if char not in _hy_reader[module_name]:
raise HyTypeError(
char,
"`{0}' is not a reader macro in module '{1}'".format(
reader_macro = _hy_reader[module_name].get(char)
if reader_macro is None:
try:
reader_macro = _hy_reader[None][char]
except KeyError:
raise HyTypeError(
char,
module_name,
),
)
"`{0}' is not a defined reader macro.".format(char)
)
expr = _hy_reader[module_name][char](tree)
expr = reader_macro(tree)
return replace_hy_obj(wrap_value(expr), tree)

View File

@ -1,3 +1,6 @@
(import [functools [wraps]])
(defn test-reader-macro []
"Test a basic redaer macro"
(defreader ^ [expr]
@ -34,3 +37,34 @@
(assert (= (, 1 2 3) a)))
(defn test-builtin-decorator-reader []
(defn increment-arguments [func]
"Increments each argument passed to the decorated function."
#@((wraps func)
(defn wrapper [&rest args &kwargs kwargs]
(apply func
(map inc args)
(dict-comp k (inc v) [[k v] (.items kwargs)])))))
#@(increment-arguments
(defn foo [&rest args &kwargs kwargs]
"Bar."
(, args kwargs)))
;; The decorator did what it was supposed to
(assert (= (, (, 2 3 4) {"quux" 5 "baz" 6})
(foo 1 2 3 :quux 4 :baz 5)))
;; @wraps preserved the doctstring and __name__
(assert (= "foo" (. foo --name--)))
(assert (= "Bar." (. foo --doc--)))
;; We can use the #@ reader macro to apply more than one decorator
#@(increment-arguments
increment-arguments
(defn double-foo [&rest args &kwargs kwargs]
"Bar."
(, args kwargs)))
(assert (= (, (, 3 4 5) {"quux" 6 "baz" 7})
(double-foo 1 2 3 :quux 4 :baz 5))))