Merge pull request #1444 from gilch/xi-tag

change xi macro to #% tag macro
This commit is contained in:
Tuukka Turto 2017-10-29 21:34:56 +02:00 committed by GitHub
commit fac87c99d0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 118 additions and 77 deletions

2
NEWS
View File

@ -22,6 +22,8 @@ Changes from 0.13.0
* support EDN `#_` syntax to discard the next term * support EDN `#_` syntax to discard the next term
* `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
* `#%` 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

@ -229,21 +229,37 @@ Returns a function which applies several forms in series from left to right. The
=> (op 2) => (op 2)
9 9
.. _xi .. _#%
xi #%
== ==
Usage ``(xi body ...)`` Usage ``#% expr``
Returns a function with parameters implicitly determined by the presence in the body of xi parameters. An xi symbol designates the ith parameter (1-based, e.g. x1, x2, x3, etc.), or all remaining parameters for xi itself. This is not a replacement for fn. The xi forms cannot be nested. Makes an expression into a function with an implicit ``%`` parameter list.
This is similar to Clojure's anonymous function literals (``#()``). 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.
.. code-block:: hy .. code-block:: hy
=> ((xi identity [x1 x5 [x2 x3] xi x4]) 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 (xi + 10 x1)) => (#% %** :foo 2)
{"foo": 2}
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) => (add-10 6)
16 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,26 +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)))
(defmacro xi [&rest body] (deftag % [expr]
"Returns a function with parameters implicitly determined by the presence in "Makes an expression into a function with an implicit `%` parameter list.
the body of xi parameters. An xi symbol designates the ith parameter
(1-based, e.g. x1, x2, x3, etc.), or all remaining parameters for xi itself. A `%i` symbol designates the (1-based) ith parameter (such as `%3`).
This is not a replacement for fn. The xi forms cannot be nested. " Only the maximum `%i` determines the number of `%i` parameters--the
(setv flatbody (flatten body)) others need not appear in the expression.
`(fn [;; generate all xi symbols up to the maximum found in body `%*` and `%**` name the `&rest` and `&kwargs` parameters, respectively.
~@(genexpr (HySymbol (+ "x"
(str i))) Nesting of `#%` forms is not recommended."
[i (range 1 (setv %symbols (set-comp a
;; find the maximum xi [a (flatten [expr])]
(inc (max (+ (list-comp (int (cut a 1)) (and (symbol? a)
[a flatbody] (.startswith a '%))))
(and (symbol? a) `(fn [;; generate all %i symbols up to the maximum found in expr
(.startswith a 'x) ~@(genexpr (HySymbol (+ "%" (str i)))
(.isdigit (cut a 1)))) [i (range 1 (-> (list-comp (int (cut a 1))
[0]))))]) [a %symbols]
;; generate the &rest parameter only if 'xi is present in body (.isdigit (cut a 1)))
~@(if (in 'xi flatbody) (or (, 0))
'(&rest xi) max
'())] inc))])
(~@body))) ;; generate the &rest parameter only if '%* is present in expr
~@(if (in '%* %symbols)
'(&rest %*))
;; similarly for &kwargs and %**
~@(if (in '%** %symbols)
'(&kwargs %**))]
~expr))

View File

@ -99,28 +99,46 @@
(assert-equal ((ap-compose (+ it 1) (* it 3)) 2) 9) (assert-equal ((ap-compose (+ it 1) (* it 3)) 2) 9)
(assert-equal ((ap-compose (list (rest it)) (len it)) [4 5 6 7]) 3)) (assert-equal ((ap-compose (list (rest it)) (len it)) [4 5 6 7]) 3))
(defn test-xi [] (defn test-tag-fn []
"NATIVE: testing xi forms" "NATIVE: testing #%() forms"
;; test ordering ;; test ordering
(assert-equal ((xi / x1 x2) 2 4) 0.5) (assert-equal (#%(/ %1 %2) 2 4) 0.5)
(assert-equal ((xi / x2 x1) 2 4) 2) (assert-equal (#%(/ %2 %1) 2 4) 2)
(assert-equal ((xi identity (, x5 x4 x3 x2 x1)) 1 2 3 4 5) (, 5 4 3 2 1)) (assert-equal (#%(identity (, %5 %4 %3 %2 %1)) 1 2 3 4 5) (, 5 4 3 2 1))
(assert-equal ((xi identity (, x1 x2 x3 x4 x5)) 1 2 3 4 5) (, 1 2 3 4 5)) (assert-equal (#%(identity (, %1 %2 %3 %4 %5)) 1 2 3 4 5) (, 1 2 3 4 5))
(assert-equal ((xi identity (, x1 x5 x2 x3 x4)) 1 2 3 4 5) (, 1 5 2 3 4)) (assert-equal (#%(identity (, %1 %5 %2 %3 %4)) 1 2 3 4 5) (, 1 5 2 3 4))
;; test &rest ;; test &rest
(assert-equal ((xi sum xi) 1 2 3) 6) (assert-equal (#%(sum %*) 1 2 3) 6)
(assert-equal ((xi identity (, x1 xi)) 10 1 2 3) (, 10 (, 1 2 3))) (assert-equal (#%(identity (, %1 %*)) 10 1 2 3) (, 10 (, 1 2 3)))
;; no parameters ;; no parameters
(assert-equal ((xi list)) []) (assert-equal (#%(list)) [])
(assert-equal ((xi identity "Hy!")) "Hy!") (assert-equal (#%(identity "Hy!")) "Hy!")
(assert-equal ((xi identity "xi")) "xi") (assert-equal (#%(identity "%*")) "%*")
(assert-equal ((xi + "Hy " "world!")) "Hy world!") (assert-equal (#%(+ "Hy " "world!")) "Hy world!")
;; test skipped parameters ;; test skipped parameters
(assert-equal ((xi identity [x3 x1]) 1 2 3) [3 1]) (assert-equal (#%(identity [%3 %1]) 1 2 3) [3 1])
;; test nesting ;; test nesting
(assert-equal ((xi identity [x1 (, x2 [x3] "Hy" [xi])]) 1 2 3 4 5) (assert-equal (#%(identity [%1 (, %2 [%3] "Hy" [%*])]) 1 2 3 4 5)
[1 (, 2 [3] "Hy" [(, 4 5)])]) [1 (, 2 [3] "Hy" [(, 4 5)])])
;; test arg as function ;; test arg as function
(assert-equal ((xi x1 2 4) +) 6) (assert-equal (#%(%1 2 4) +) 6)
(assert-equal ((xi x1 2 4) -) -2) (assert-equal (#%(%1 2 4) -) -2)
(assert-equal ((xi x1 2 4) /) 0.5)) (assert-equal (#%(%1 2 4) /) 0.5)
;; test &rest &kwargs
(assert-equal (#%(, %* %**) 1 2 :a 'b)
(, (, 1 2)
(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"))