generalize #% to arbitrary expressions

This commit is contained in:
gilch 2017-10-25 20:46:38 -06:00
parent b5f1136ba5
commit 497e929913
4 changed files with 64 additions and 36 deletions

2
NEWS
View File

@ -23,7 +23,7 @@ Changes from 0.13.0
* `return` has been implemented as a special form * `return` has been implemented as a special form
* `while` loops may now contain an `else` clause, like `for` loops * `while` loops may now contain an `else` clause, like `for` loops
* `xi` from `hy.extra.anaphoric` is now the `#%` tag macro * `xi` from `hy.extra.anaphoric` is now the `#%` tag macro
* `#%` also has a new `&kwargs` parameter `%**`. * `#%` works on any expression and has a new `&kwargs` parameter `%**`
[ Bug Fixes ] [ Bug Fixes ]
* Numeric literals are no longer parsed as symbols when followed by a dot * Numeric literals are no longer parsed as symbols when followed by a dot

View File

@ -234,20 +234,32 @@ Returns a function which applies several forms in series from left to right. The
#% #%
== ==
Usage ``#%(body ...)`` Usage ``#% expr``
Makes a function with an implicit parameter list from ``%`` parameters. Makes an expression into a function with an implicit ``%`` parameter list.
A ``%i`` symbol designates the `i` th parameter (1-based, e.g. ``%1 %2 %3`` etc.). A ``%i`` symbol designates the (1-based) *i* th parameter (such as ``%3``).
Only the maximum ``%i`` determines the number of ``%i`` parameters--the
others need not appear in the expression.
``%*`` and ``%**`` name the ``&rest`` and ``&kwargs`` parameters, respectively. ``%*`` and ``%**`` name the ``&rest`` and ``&kwargs`` parameters, respectively.
Nesting of ``#%()`` forms is not recommended.
This is similar to Clojure's anonymous function literals (``#()``).
.. code-block:: hy .. code-block:: hy
=> (#%(identity [%1 %5 [%2 %3] %* %4]) 1 2 3 4 5 6 7 8) => (#%[%1 %6 42 [%2 %3] %* %4] 1 2 3 4 555 6 7 8)
[1, 5, [2, 3,] (6, 7, 8), 4] [1, 6, 42, [2, 3], (7, 8), 4]
=> (def add-10 #%(+ 10 %1)) => (#% %** :foo 2)
=> (add-10 6) {"foo": 2}
16
When used on an s-expression,
``#%`` is similar to Clojure's anonymous function literals--``#()``.
.. code-block:: hy
=> (setv add-10 #%(+ 10 %1))
=> (add-10 6)
16
``#%`` determines the parameter list by the presence of a ``%*`` or ``%**``
symbol and by the maximum ``%i`` symbol found *anywhere* in the expression,
so nesting of ``#%`` forms is not recommended.

View File

@ -5,7 +5,6 @@
;;; These macros make writing functional programs more concise ;;; These macros make writing functional programs more concise
(defmacro ap-if [test-form then-form &optional else-form] (defmacro ap-if [test-form then-form &optional else-form]
`(do `(do
(setv it ~test-form) (setv it ~test-form)
@ -112,29 +111,32 @@
"Returns a function which is the composition of several forms." "Returns a function which is the composition of several forms."
`(fn [var] (ap-pipe var ~@forms))) `(fn [var] (ap-pipe var ~@forms)))
(deftag % [body] (deftag % [expr]
"makes a function with an implicit parameter list from `%` parameters. "Makes an expression into a function with an implicit `%` parameter list.
A %i symbol designates the ith parameter (1-based, e.g. `%1 %2 %3` etc.). A `%i` symbol designates the (1-based) ith parameter (such as `%3`).
Only the maximum `%i` determines the number of `%i` parameters--the
others need not appear in the expression.
`%*` and `%**` name the `&rest` and `&kwargs` parameters, respectively. `%*` and `%**` name the `&rest` and `&kwargs` parameters, respectively.
Nesting of `#%()` forms is not recommended."
(setv flatbody (flatten body)) Nesting of `#%` forms is not recommended."
`(fn [;; generate all %i symbols up to the maximum found in body (setv %symbols (set-comp a
~@(genexpr (HySymbol (+ "%" [a (flatten [expr])]
(str i))) (and (symbol? a)
[i (range 1 (.startswith a '%))))
;; find the maximum %i `(fn [;; generate all %i symbols up to the maximum found in expr
(-> (list-comp (int (cut a 1)) ~@(genexpr (HySymbol (+ "%" (str i)))
[a flatbody] [i (range 1 (-> (list-comp (int (cut a 1))
(and (symbol? a) [a %symbols]
(.startswith a '%) (.isdigit (cut a 1)))
(.isdigit (cut a 1)))) (or (, 0))
(+ [0]) max
max inc))])
inc))]) ;; generate the &rest parameter only if '%* is present in expr
;; generate the &rest parameter only if '%* is present in body ~@(if (in '%* %symbols)
~@(if (in '%* flatbody)
'(&rest %*)) '(&rest %*))
~@(if (in '%** flatbody) ;; similarly for &kwargs and %**
~@(if (in '%** %symbols)
'(&kwargs %**))] '(&kwargs %**))]
(~@body))) ~expr))

View File

@ -127,4 +127,18 @@
;; test &rest &kwargs ;; test &rest &kwargs
(assert-equal (#%(, %* %**) 1 2 :a 'b) (assert-equal (#%(, %* %**) 1 2 :a 'b)
(, (, 1 2) (, (, 1 2)
(dict :a 'b)))) (dict :a 'b)))
;; test other expression types
(assert-equal (#% %* 1 2 3)
(, 1 2 3))
(assert-equal (#% %** :foo 2)
(dict :foo 2))
(assert-equal (#%[%3 %2 %1] 1 2 3)
[3 2 1])
(assert-equal (#%{%1 %2} 10 100)
{10 100})
(assert-equal (#% #{%3 %2 %1} 1 3 2)
#{3 1 2}) ; sets are not ordered.
(assert-equal (#% "%1")
"%1"))