diff --git a/NEWS b/NEWS index ce8abaf..4be51d4 100644 --- a/NEWS +++ b/NEWS @@ -22,6 +22,8 @@ Changes from 0.13.0 * support EDN `#_` syntax to discard the next term * `return` has been implemented as a special form * `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 ] * Numeric literals are no longer parsed as symbols when followed by a dot diff --git a/docs/extra/anaphoric.rst b/docs/extra/anaphoric.rst index 68cb709..a630eae 100644 --- a/docs/extra/anaphoric.rst +++ b/docs/extra/anaphoric.rst @@ -229,21 +229,37 @@ Returns a function which applies several forms in series from left to right. The => (op 2) 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 - => ((xi identity [x1 x5 [x2 x3] xi x4]) 1 2 3 4 5 6 7 8) - [1, 5, [2, 3,] (6, 7, 8), 4] - => (def add-10 (xi + 10 x1)) - => (add-10 6) - 16 + => (#%[%1 %6 42 [%2 %3] %* %4] 1 2 3 4 555 6 7 8) + [1, 6, 42, [2, 3], (7, 8), 4] + => (#% %** :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) + 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. + diff --git a/hy/extra/anaphoric.hy b/hy/extra/anaphoric.hy index 2772818..9ad92b7 100644 --- a/hy/extra/anaphoric.hy +++ b/hy/extra/anaphoric.hy @@ -5,11 +5,10 @@ ;;; These macros make writing functional programs more concise - (defmacro ap-if [test-form then-form &optional else-form] `(do - (setv it ~test-form) - (if it ~then-form ~else-form))) + (setv it ~test-form) + (if it ~then-form ~else-form))) (defmacro ap-each [lst &rest body] @@ -25,17 +24,17 @@ (defn ~p [it] ~form) (for [it ~lst] (if (~p it) - ~@body - (break))))) + ~@body + (break))))) (defmacro ap-map [form lst] "Yield elements evaluated in the form for each element in the list." (setv v (gensym 'v) f (gensym 'f)) `((fn [] - (defn ~f [it] ~form) - (for [~v ~lst] - (yield (~f ~v)))))) + (defn ~f [it] ~form) + (for [~v ~lst] + (yield (~f ~v)))))) (defmacro ap-map-when [predfn rep lst] @@ -43,21 +42,21 @@ predicate function returns True." (setv f (gensym)) `((fn [] - (defn ~f [it] ~rep) - (for [it ~lst] - (if (~predfn it) - (yield (~f it)) - (yield it)))))) + (defn ~f [it] ~rep) + (for [it ~lst] + (if (~predfn it) + (yield (~f it)) + (yield it)))))) (defmacro ap-filter [form lst] "Yield elements returned when the predicate form evaluates to True." (setv pred (gensym)) `((fn [] - (defn ~pred [it] ~form) - (for [val ~lst] - (if (~pred val) - (yield val)))))) + (defn ~pred [it] ~form) + (for [val ~lst] + (if (~pred val) + (yield val)))))) (defmacro ap-reject [form lst] @@ -95,10 +94,10 @@ (defmacro ap-reduce [form lst &optional [initial-value None]] "Anaphoric form of reduce, `acc' and `it' can be used for a form" `(do - (setv acc ~(if (none? initial-value) `(get ~lst 0) initial-value)) - (ap-each ~(if (none? initial-value) `(cut ~lst 1) lst) - (setv acc ~form)) - acc)) + (setv acc ~(if (none? initial-value) `(get ~lst 0) initial-value)) + (ap-each ~(if (none? initial-value) `(cut ~lst 1) lst) + (setv acc ~form)) + acc)) (defmacro ap-pipe [var &rest forms] @@ -112,26 +111,32 @@ "Returns a function which is the composition of several forms." `(fn [var] (ap-pipe var ~@forms))) -(defmacro xi [&rest body] - "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. " - (setv flatbody (flatten body)) - `(fn [;; generate all xi symbols up to the maximum found in body - ~@(genexpr (HySymbol (+ "x" - (str i))) - [i (range 1 - ;; find the maximum xi - (inc (max (+ (list-comp (int (cut a 1)) - [a flatbody] - (and (symbol? a) - (.startswith a 'x) - (.isdigit (cut a 1)))) - [0]))))]) - ;; generate the &rest parameter only if 'xi is present in body - ~@(if (in 'xi flatbody) - '(&rest xi) - '())] - (~@body))) +(deftag % [expr] + "Makes an expression into a function with an implicit `%` parameter list. + + 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. + + Nesting of `#%` forms is not recommended." + (setv %symbols (set-comp a + [a (flatten [expr])] + (and (symbol? a) + (.startswith a '%)))) + `(fn [;; generate all %i symbols up to the maximum found in expr + ~@(genexpr (HySymbol (+ "%" (str i))) + [i (range 1 (-> (list-comp (int (cut a 1)) + [a %symbols] + (.isdigit (cut a 1))) + (or (, 0)) + max + inc))]) + ;; generate the &rest parameter only if '%* is present in expr + ~@(if (in '%* %symbols) + '(&rest %*)) + ;; similarly for &kwargs and %** + ~@(if (in '%** %symbols) + '(&kwargs %**))] + ~expr)) diff --git a/tests/native_tests/extra/anaphoric.hy b/tests/native_tests/extra/anaphoric.hy index d772029..76bf635 100644 --- a/tests/native_tests/extra/anaphoric.hy +++ b/tests/native_tests/extra/anaphoric.hy @@ -65,9 +65,9 @@ (defn test-ap-dotimes [] "NATIVE: testing anaphoric dotimes" (assert-equal (do (setv n []) (ap-dotimes 3 (.append n 3)) n) - [3 3 3]) + [3 3 3]) (assert-equal (do (setv n []) (ap-dotimes 3 (.append n it)) n) - [0 1 2])) + [0 1 2])) (defn test-ap-first [] "NATIVE: testing anaphoric first" @@ -86,41 +86,59 @@ (assert-equal (ap-reduce (* acc it) [1 2 3]) 6) (assert-equal (ap-reduce (* acc it) [1 2 3] 6) 36) (assert-equal (ap-reduce (+ acc " on " it) ["Hy" "meth"]) - "Hy on meth") + "Hy on meth") (assert-equal (ap-reduce (+ acc it) [] 1) 1)) - + (defn test-ap-pipe [] "NATIVE: testing anaphoric pipe" (assert-equal (ap-pipe 2 (+ it 1) (* it 3)) 9) (assert-equal (ap-pipe [4 5 6 7] (list (rest it)) (len it)) 3)) - + (defn test-ap-compose [] - "NATIVE: testing anaphoric compose" + "NATIVE: testing anaphoric compose" (assert-equal ((ap-compose (+ it 1) (* it 3)) 2) 9) (assert-equal ((ap-compose (list (rest it)) (len it)) [4 5 6 7]) 3)) -(defn test-xi [] - "NATIVE: testing xi forms" +(defn test-tag-fn [] + "NATIVE: testing #%() forms" ;; test ordering - (assert-equal ((xi / x1 x2) 2 4) 0.5) - (assert-equal ((xi / x2 x1) 2 4) 2) - (assert-equal ((xi identity (, x5 x4 x3 x2 x1)) 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 ((xi identity (, x1 x5 x2 x3 x4)) 1 2 3 4 5) (, 1 5 2 3 4)) + (assert-equal (#%(/ %1 %2) 2 4) 0.5) + (assert-equal (#%(/ %2 %1) 2 4) 2) + (assert-equal (#%(identity (, %5 %4 %3 %2 %1)) 1 2 3 4 5) (, 5 4 3 2 1)) + (assert-equal (#%(identity (, %1 %2 %3 %4 %5)) 1 2 3 4 5) (, 1 2 3 4 5)) + (assert-equal (#%(identity (, %1 %5 %2 %3 %4)) 1 2 3 4 5) (, 1 5 2 3 4)) ;; test &rest - (assert-equal ((xi sum xi) 1 2 3) 6) - (assert-equal ((xi identity (, x1 xi)) 10 1 2 3) (, 10 (, 1 2 3))) + (assert-equal (#%(sum %*) 1 2 3) 6) + (assert-equal (#%(identity (, %1 %*)) 10 1 2 3) (, 10 (, 1 2 3))) ;; no parameters - (assert-equal ((xi list)) []) - (assert-equal ((xi identity "Hy!")) "Hy!") - (assert-equal ((xi identity "xi")) "xi") - (assert-equal ((xi + "Hy " "world!")) "Hy world!") + (assert-equal (#%(list)) []) + (assert-equal (#%(identity "Hy!")) "Hy!") + (assert-equal (#%(identity "%*")) "%*") + (assert-equal (#%(+ "Hy " "world!")) "Hy world!") ;; 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 - (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)])]) ;; test arg as function - (assert-equal ((xi x1 2 4) +) 6) - (assert-equal ((xi x1 2 4) -) -2) - (assert-equal ((xi x1 2 4) /) 0.5)) + (assert-equal (#%(%1 2 4) +) 6) + (assert-equal (#%(%1 2 4) -) -2) + (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")) +