Merge pull request #1855 from Kodiologist/anaphix
Overhaul hy.extra.anaphoric
This commit is contained in:
commit
42100b1658
@ -17,15 +17,27 @@ To use these macros you need to require the ``hy.extra.anaphoric`` module like s
|
|||||||
|
|
||||||
``(require [hy.extra.anaphoric [*]])``
|
``(require [hy.extra.anaphoric [*]])``
|
||||||
|
|
||||||
|
These macros are implemented by replacing any use of the designated
|
||||||
|
anaphoric symbols (``it``, in most cases) with a gensym. Consequently,
|
||||||
|
it's unwise to nest these macros, or to use an affected symbol as
|
||||||
|
something other than a variable name, as in ``(print "My favorite
|
||||||
|
Stephen King book is" 'it)``.
|
||||||
|
|
||||||
.. _ap-if:
|
.. _ap-if:
|
||||||
|
|
||||||
ap-if
|
ap-if
|
||||||
=====
|
=====
|
||||||
|
|
||||||
Usage: ``(ap-if (foo) (print it))``
|
Usage: ``(ap-if test-form then-form else-form)``
|
||||||
|
|
||||||
Evaluates the first form for truthiness, and bind it to ``it`` in both the
|
As :ref:`if <if>`, but the result of the test form is named ``it`` in
|
||||||
true and false branches.
|
the subsequent forms. As with ``if``, the else-clause is optional.
|
||||||
|
|
||||||
|
.. code-block:: hy
|
||||||
|
|
||||||
|
=> (import os)
|
||||||
|
=> (ap-if (.get os.environ "PYTHONPATH")
|
||||||
|
... (print "Your PYTHONPATH is" it))
|
||||||
|
|
||||||
|
|
||||||
.. _ap-each:
|
.. _ap-each:
|
||||||
@ -33,9 +45,17 @@ true and false branches.
|
|||||||
ap-each
|
ap-each
|
||||||
=======
|
=======
|
||||||
|
|
||||||
Usage: ``(ap-each [1 2 3 4 5] (print it))``
|
Usage: ``(ap-each xs body…)``
|
||||||
|
|
||||||
Evaluate the form for each element in the list for side-effects.
|
Evaluate the body forms for each element ``it`` of ``xs`` and return
|
||||||
|
``None``.
|
||||||
|
|
||||||
|
.. code-block:: hy
|
||||||
|
|
||||||
|
=> (ap-each [1 2 3] (print it))
|
||||||
|
1
|
||||||
|
2
|
||||||
|
3
|
||||||
|
|
||||||
|
|
||||||
.. _ap-each-while:
|
.. _ap-each-while:
|
||||||
@ -43,10 +63,10 @@ Evaluate the form for each element in the list for side-effects.
|
|||||||
ap-each-while
|
ap-each-while
|
||||||
=============
|
=============
|
||||||
|
|
||||||
Usage: ``(ap-each-while list pred body)``
|
Usage: ``(ap-each-while xs pred body…)``
|
||||||
|
|
||||||
Evaluate the form for each element where the predicate form returns
|
As ``ap-each``, but the form ``pred`` is run before the body forms on
|
||||||
``True``.
|
each iteration, and the loop ends if ``pred`` is false.
|
||||||
|
|
||||||
.. code-block:: hy
|
.. code-block:: hy
|
||||||
|
|
||||||
@ -60,11 +80,10 @@ Evaluate the form for each element where the predicate form returns
|
|||||||
ap-map
|
ap-map
|
||||||
======
|
======
|
||||||
|
|
||||||
Usage: ``(ap-map form list)``
|
Usage: ``(ap-map form xs)``
|
||||||
|
|
||||||
The anaphoric form of map works just like regular map except that
|
Create a generator like :py:func:`map` that yields each result of ``form``
|
||||||
instead of a function object it takes a Hy form. The special name
|
evaluated with ``it`` bound to successive elements of ``xs``.
|
||||||
``it`` is bound to the current object from the list in the iteration.
|
|
||||||
|
|
||||||
.. code-block:: hy
|
.. code-block:: hy
|
||||||
|
|
||||||
@ -77,10 +96,12 @@ instead of a function object it takes a Hy form. The special name
|
|||||||
ap-map-when
|
ap-map-when
|
||||||
===========
|
===========
|
||||||
|
|
||||||
Usage: ``(ap-map-when predfn rep list)``
|
Usage: ``(ap-map-when predfn rep xs)``
|
||||||
|
|
||||||
Evaluate a mapping over the list using a predicate function to
|
As ``ap-map``, but the predicate function ``predfn`` (yes, that's a
|
||||||
determin when to apply the form.
|
function, not an anaphoric form) is applied to each ``it``, and the
|
||||||
|
anaphoric mapping form ``rep`` is only applied if the predicate is true.
|
||||||
|
Otherwise, ``it`` is yielded unchanged.
|
||||||
|
|
||||||
.. code-block:: hy
|
.. code-block:: hy
|
||||||
|
|
||||||
@ -96,11 +117,9 @@ determin when to apply the form.
|
|||||||
ap-filter
|
ap-filter
|
||||||
=========
|
=========
|
||||||
|
|
||||||
Usage: ``(ap-filter form list)``
|
Usage: ``(ap-filter form xs)``
|
||||||
|
|
||||||
As with ``ap-map`` we take a special form instead of a function to
|
The :py:func:`filter` equivalent of ``ap-map``.
|
||||||
filter the elements of the list. The special name ``it`` is bound to
|
|
||||||
the current element in the iteration.
|
|
||||||
|
|
||||||
.. code-block:: hy
|
.. code-block:: hy
|
||||||
|
|
||||||
@ -113,11 +132,9 @@ the current element in the iteration.
|
|||||||
ap-reject
|
ap-reject
|
||||||
=========
|
=========
|
||||||
|
|
||||||
Usage: ``(ap-reject form list)``
|
Usage: ``(ap-reject form xs)``
|
||||||
|
|
||||||
This function does the opposite of ``ap-filter``, it rejects the
|
Equivalent to ``(ap-filter (not form) xs)``.
|
||||||
elements passing the predicate . The special name ``it`` is bound to
|
|
||||||
the current element in the iteration.
|
|
||||||
|
|
||||||
.. code-block:: hy
|
.. code-block:: hy
|
||||||
|
|
||||||
@ -130,10 +147,9 @@ the current element in the iteration.
|
|||||||
ap-dotimes
|
ap-dotimes
|
||||||
==========
|
==========
|
||||||
|
|
||||||
Usage ``(ap-dotimes n body)``
|
Usage: ``(ap-dotimes n body…)``
|
||||||
|
|
||||||
This function evaluates the body *n* times, with the special
|
Equivalent to ``(ap-each (range n) body…)``.
|
||||||
variable ``it`` bound from *0* to *1-n*. It is useful for side-effects.
|
|
||||||
|
|
||||||
.. code-block:: hy
|
.. code-block:: hy
|
||||||
|
|
||||||
@ -148,15 +164,15 @@ variable ``it`` bound from *0* to *1-n*. It is useful for side-effects.
|
|||||||
ap-first
|
ap-first
|
||||||
========
|
========
|
||||||
|
|
||||||
Usage ``(ap-first predfn list)``
|
Usage: ``(ap-first form xs)``
|
||||||
|
|
||||||
This function returns the first element that passes the predicate or
|
Evaluate the predicate ``form`` for each element ``it`` of ``xs``. When
|
||||||
``None``, with the special variable ``it`` bound to the current element in
|
the predicate is true, stop and return ``it``. If the predicate is never
|
||||||
iteration.
|
true, return ``None``.
|
||||||
|
|
||||||
.. code-block:: hy
|
.. code-block:: hy
|
||||||
|
|
||||||
=>(ap-first (> it 5) (range 10))
|
=> (ap-first (> it 5) (range 10))
|
||||||
6
|
6
|
||||||
|
|
||||||
|
|
||||||
@ -165,15 +181,15 @@ iteration.
|
|||||||
ap-last
|
ap-last
|
||||||
========
|
========
|
||||||
|
|
||||||
Usage ``(ap-last predfn list)``
|
Usage: ``(ap-last form list)``
|
||||||
|
|
||||||
This function returns the last element that passes the predicate or
|
Evaluate the predicate ``form`` for every element ``it`` of ``xs``.
|
||||||
``None``, with the special variable ``it`` bound to the current element in
|
Return the last element for which the predicate is true, or ``None`` if
|
||||||
iteration.
|
there is no such element.
|
||||||
|
|
||||||
.. code-block:: hy
|
.. code-block:: hy
|
||||||
|
|
||||||
=>(ap-last (> it 5) (range 10))
|
=> (ap-last (> it 5) (range 10))
|
||||||
9
|
9
|
||||||
|
|
||||||
|
|
||||||
@ -182,18 +198,24 @@ iteration.
|
|||||||
ap-reduce
|
ap-reduce
|
||||||
=========
|
=========
|
||||||
|
|
||||||
Usage ``(ap-reduce form list &optional initial-value)``
|
Usage: ``(ap-reduce form xs &optional initial-value)``
|
||||||
|
|
||||||
This function returns the result of applying form to the first 2
|
This macro is an anaphoric version of :py:func:`reduce`. It works as
|
||||||
elements in the body and applying the result and the 3rd element
|
follows:
|
||||||
etc. until the list is exhausted. Optionally an initial value can be
|
|
||||||
supplied so the function will be applied to initial value and the
|
- Bind ``acc`` to the first element of ``xs``, bind ``it`` to the
|
||||||
first element instead. This exposes the element being iterated as
|
second, and evaluate ``form``.
|
||||||
``it`` and the current accumulated value as ``acc``.
|
- Bind ``acc`` to the result, bind ``it`` to the third value of ``xs``,
|
||||||
|
and evaluate ``form`` again.
|
||||||
|
- Bind ``acc`` to the result, and continue until ``xs`` is exhausted.
|
||||||
|
|
||||||
|
If ``initial-value`` is supplied, the process instead begins with
|
||||||
|
``acc`` set to ``initial-value`` and ``it`` set to the first element of
|
||||||
|
``xs``.
|
||||||
|
|
||||||
.. code-block:: hy
|
.. code-block:: hy
|
||||||
|
|
||||||
=>(ap-reduce (+ it acc) (range 10))
|
=> (ap-reduce (+ it acc) (range 10))
|
||||||
45
|
45
|
||||||
|
|
||||||
|
|
||||||
@ -202,7 +224,7 @@ first element instead. This exposes the element being iterated as
|
|||||||
#%
|
#%
|
||||||
==
|
==
|
||||||
|
|
||||||
Usage ``#% expr``
|
Usage: ``#% expr``
|
||||||
|
|
||||||
Makes an expression into a function with an implicit ``%`` parameter list.
|
Makes an expression into a function with an implicit ``%`` parameter list.
|
||||||
|
|
||||||
|
@ -6,112 +6,71 @@
|
|||||||
;;; 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
|
(rit `(do
|
||||||
(setv it ~test-form)
|
(setv it ~test-form)
|
||||||
(if it ~then-form ~else-form)))
|
(if it ~then-form ~else-form))))
|
||||||
|
|
||||||
|
|
||||||
(defmacro ap-each [lst &rest body]
|
(defmacro ap-each [xs &rest body]
|
||||||
"Evaluate the body form for each element in the list."
|
(rit `(for [it ~xs] ~@body)))
|
||||||
`(for [it ~lst] ~@body))
|
|
||||||
|
|
||||||
|
|
||||||
(defmacro ap-each-while [lst form &rest body]
|
(defmacro ap-each-while [xs form &rest body]
|
||||||
"Evaluate the body form for each element in the list while the
|
(rit `(for [it ~xs]
|
||||||
predicate form evaluates to True."
|
(unless ~form
|
||||||
(setv p (gensym))
|
(break))
|
||||||
`(do
|
~@body)))
|
||||||
(defn ~p [it] ~form)
|
|
||||||
(for [it ~lst]
|
|
||||||
(if (~p it)
|
|
||||||
~@body
|
|
||||||
(break)))))
|
|
||||||
|
|
||||||
|
|
||||||
(defmacro ap-map [form lst]
|
(defmacro ap-map [form xs]
|
||||||
"Yield elements evaluated in the form for each element in the list."
|
(rit `(gfor it ~xs ~form)))
|
||||||
(setv v (gensym 'v) f (gensym 'f))
|
|
||||||
`((fn []
|
|
||||||
(defn ~f [it] ~form)
|
|
||||||
(for [~v ~lst]
|
|
||||||
(yield (~f ~v))))))
|
|
||||||
|
|
||||||
|
|
||||||
(defmacro ap-map-when [predfn rep lst]
|
(defmacro ap-map-when [predfn rep xs]
|
||||||
"Yield elements evaluated for each element in the list when the
|
(rit `(gfor it ~xs (if (~predfn it) ~rep it))))
|
||||||
predicate function returns True."
|
|
||||||
(setv f (gensym))
|
|
||||||
`((fn []
|
|
||||||
(defn ~f [it] ~rep)
|
|
||||||
(for [it ~lst]
|
|
||||||
(if (~predfn it)
|
|
||||||
(yield (~f it))
|
|
||||||
(yield it))))))
|
|
||||||
|
|
||||||
|
|
||||||
(defmacro ap-filter [form lst]
|
(defmacro ap-filter [form xs]
|
||||||
"Yield elements returned when the predicate form evaluates to True."
|
(rit `(gfor it ~xs :if ~form it)))
|
||||||
(setv pred (gensym))
|
|
||||||
`((fn []
|
|
||||||
(defn ~pred [it] ~form)
|
|
||||||
(for [val ~lst]
|
|
||||||
(if (~pred val)
|
|
||||||
(yield val))))))
|
|
||||||
|
|
||||||
|
|
||||||
(defmacro ap-reject [form lst]
|
(defmacro ap-reject [form xs]
|
||||||
"Yield elements returned when the predicate form evaluates to False"
|
(rit `(gfor it ~xs :if (not ~form) it)))
|
||||||
`(ap-filter (not ~form) ~lst))
|
|
||||||
|
|
||||||
|
|
||||||
(defmacro ap-dotimes [n &rest body]
|
(defmacro ap-dotimes [n &rest body]
|
||||||
"Execute body for side effects `n' times, with it bound from 0 to n-1"
|
(rit `(for [it (range ~n)]
|
||||||
(unless (numeric? n)
|
~@body)))
|
||||||
(raise (TypeError (.format "{!r} is not a number" n))))
|
|
||||||
`(ap-each (range ~n) ~@body))
|
|
||||||
|
|
||||||
|
|
||||||
(defmacro ap-first [predfn lst]
|
(defmacro ap-first [form xs]
|
||||||
"Yield the first element that passes `predfn`"
|
(rit `(next
|
||||||
(with-gensyms [n]
|
(gfor it ~xs :if ~form it)
|
||||||
`(do
|
None)))
|
||||||
(setv ~n None)
|
|
||||||
(ap-each ~lst (when ~predfn (setv ~n it) (break)))
|
|
||||||
~n)))
|
|
||||||
|
|
||||||
|
|
||||||
(defmacro ap-last [predfn lst]
|
(defmacro ap-last [form xs]
|
||||||
"Yield the last element that passes `predfn`"
|
(setv x (gensym))
|
||||||
(with-gensyms [n]
|
(rit `(do
|
||||||
`(do
|
(setv ~x None)
|
||||||
(setv ~n None)
|
(for [it ~xs :if ~form]
|
||||||
(ap-each ~lst (none? ~n)
|
(setv ~x it))
|
||||||
(when ~predfn
|
~x)))
|
||||||
(setv ~n it)))
|
|
||||||
~n)))
|
|
||||||
|
|
||||||
|
|
||||||
(defmacro! ap-reduce [form o!lst &optional [initial-value None]]
|
(defmacro! ap-reduce [form o!xs &optional [initial-value None]]
|
||||||
"Anaphoric form of reduce, `acc' and `it' can be used for a form"
|
(recur-sym-replace {'it (gensym) 'acc (gensym)} `(do
|
||||||
`(do
|
|
||||||
(setv acc ~(if (none? initial-value)
|
(setv acc ~(if (none? initial-value)
|
||||||
`(do
|
`(do
|
||||||
(setv ~g!lst (iter ~g!lst))
|
(setv ~g!xs (iter ~g!xs))
|
||||||
(next ~g!lst))
|
(next ~g!xs))
|
||||||
initial-value))
|
initial-value))
|
||||||
(ap-each ~g!lst (setv acc ~form))
|
(for [it ~g!xs]
|
||||||
acc))
|
(setv acc ~form))
|
||||||
|
acc)))
|
||||||
|
|
||||||
|
|
||||||
(deftag % [expr]
|
(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 (sfor a (flatten [expr])
|
(setv %symbols (sfor a (flatten [expr])
|
||||||
:if (and (symbol? a)
|
:if (and (symbol? a)
|
||||||
(.startswith a '%))
|
(.startswith a '%))
|
||||||
@ -132,3 +91,23 @@
|
|||||||
'(&kwargs %**))]
|
'(&kwargs %**))]
|
||||||
~expr))
|
~expr))
|
||||||
|
|
||||||
|
|
||||||
|
;;; --------------------------------------------------
|
||||||
|
;;; Subroutines
|
||||||
|
;;; --------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
(defn recur-sym-replace [d form]
|
||||||
|
"Recursive symbol replacement."
|
||||||
|
(cond
|
||||||
|
[(instance? HySymbol form)
|
||||||
|
(.get d form form)]
|
||||||
|
[(coll? form)
|
||||||
|
((type form) (gfor x form (recur-sym-replace d x)))]
|
||||||
|
[True
|
||||||
|
form]))
|
||||||
|
|
||||||
|
|
||||||
|
(defn rit [form]
|
||||||
|
"Replace `it` with a gensym throughout `form`."
|
||||||
|
(recur-sym-replace {'it (gensym)} form))
|
||||||
|
@ -7,11 +7,17 @@
|
|||||||
|
|
||||||
(defn test-ap-if []
|
(defn test-ap-if []
|
||||||
(ap-if True (assert (is it True)))
|
(ap-if True (assert (is it True)))
|
||||||
(ap-if False True (assert (is it False))))
|
(ap-if False True (assert (is it False)))
|
||||||
|
|
||||||
|
; https://github.com/hylang/hy/issues/1847
|
||||||
|
(setv it "orig")
|
||||||
|
(setv out (ap-if (+ 1 1) (+ it 1) (+ it 10)))
|
||||||
|
(assert (= out 3))
|
||||||
|
(assert (= it "orig")))
|
||||||
|
|
||||||
(defn test-ap-each []
|
(defn test-ap-each []
|
||||||
(setv res [])
|
(setv res [])
|
||||||
(ap-each [1 2 3 4] (.append res it))
|
(assert (is (ap-each [1 2 3 4] (.append res it)) None))
|
||||||
(assert (= res [1 2 3 4])))
|
(assert (= res [1 2 3 4])))
|
||||||
|
|
||||||
(defn test-ap-each-while []
|
(defn test-ap-each-while []
|
||||||
@ -47,7 +53,13 @@
|
|||||||
(assert (= (do (setv n []) (ap-dotimes 3 (.append n 3)) n)
|
(assert (= (do (setv n []) (ap-dotimes 3 (.append n 3)) n)
|
||||||
[3 3 3]))
|
[3 3 3]))
|
||||||
(assert (= (do (setv n []) (ap-dotimes 3 (.append n it)) n)
|
(assert (= (do (setv n []) (ap-dotimes 3 (.append n it)) n)
|
||||||
[0 1 2])))
|
[0 1 2]))
|
||||||
|
|
||||||
|
; https://github.com/hylang/hy/issues/1853
|
||||||
|
(setv n 5)
|
||||||
|
(setv x "")
|
||||||
|
(ap-dotimes n (+= x "."))
|
||||||
|
(assert (= x ".....")))
|
||||||
|
|
||||||
(defn test-ap-first []
|
(defn test-ap-first []
|
||||||
(assert (= (ap-first (> it 5) (range 10)) 6))
|
(assert (= (ap-first (> it 5) (range 10)) 6))
|
||||||
|
11
tests/native_tests/extra/anaphoric_single.hy
Normal file
11
tests/native_tests/extra/anaphoric_single.hy
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
;; Copyright 2019 the authors.
|
||||||
|
;; This file is part of Hy, which is free software licensed under the Expat
|
||||||
|
;; license. See the LICENSE.
|
||||||
|
|
||||||
|
(require [hy.extra.anaphoric [ap-last]])
|
||||||
|
|
||||||
|
(defn test-anaphoric-single-require []
|
||||||
|
; https://github.com/hylang/hy/issues/1853#issuecomment-568192529
|
||||||
|
; `ap-last` should work even if `require`d without anything else
|
||||||
|
; from the anaphoric module.
|
||||||
|
(assert (= (ap-last (> it 0) [-1 1 0 3 2 0 -1]) 2)))
|
Loading…
Reference in New Issue
Block a user