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:
parent
f247056360
commit
016557deab
@ -1463,6 +1463,25 @@ will be 4 (``1+1 + 1+1``).
|
|||||||
8
|
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:
|
||||||
|
|
||||||
with-gensyms
|
with-gensyms
|
||||||
|
@ -231,3 +231,9 @@
|
|||||||
(repeat r.text)))
|
(repeat r.text)))
|
||||||
(catch [e ImportError]
|
(catch [e ImportError]
|
||||||
(repeat "Botsbuildbots requires `requests' to function."))))
|
(repeat "Botsbuildbots requires `requests' to function."))))
|
||||||
|
|
||||||
|
|
||||||
|
(defreader @ [expr]
|
||||||
|
(let [[decorators (slice expr nil -1)]
|
||||||
|
[fndef (get expr -1)]]
|
||||||
|
`(with-decorator ~@decorators ~fndef)))
|
||||||
|
21
hy/macros.py
21
hy/macros.py
@ -60,7 +60,7 @@ def macro(name):
|
|||||||
|
|
||||||
|
|
||||||
def reader(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
|
This stores the macro `name` in the namespace for the module where it is
|
||||||
defined.
|
defined.
|
||||||
@ -68,7 +68,7 @@ def reader(name):
|
|||||||
If the module where it is defined is in `hy.core`, then the macro is stored
|
If the module where it is defined is in `hy.core`, then the macro is stored
|
||||||
in the default `None` namespace.
|
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):
|
def _(fn):
|
||||||
@ -176,14 +176,15 @@ def reader_macroexpand(char, tree, module_name):
|
|||||||
"""Expand the reader macro "char" with argument `tree`."""
|
"""Expand the reader macro "char" with argument `tree`."""
|
||||||
load_macros(module_name)
|
load_macros(module_name)
|
||||||
|
|
||||||
if char not in _hy_reader[module_name]:
|
reader_macro = _hy_reader[module_name].get(char)
|
||||||
raise HyTypeError(
|
if reader_macro is None:
|
||||||
char,
|
try:
|
||||||
"`{0}' is not a reader macro in module '{1}'".format(
|
reader_macro = _hy_reader[None][char]
|
||||||
|
except KeyError:
|
||||||
|
raise HyTypeError(
|
||||||
char,
|
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)
|
return replace_hy_obj(wrap_value(expr), tree)
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
|
(import [functools [wraps]])
|
||||||
|
|
||||||
|
|
||||||
(defn test-reader-macro []
|
(defn test-reader-macro []
|
||||||
"Test a basic redaer macro"
|
"Test a basic redaer macro"
|
||||||
(defreader ^ [expr]
|
(defreader ^ [expr]
|
||||||
@ -34,3 +37,34 @@
|
|||||||
(assert (= (, 1 2 3) a)))
|
(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))))
|
||||||
|
Loading…
Reference in New Issue
Block a user