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 [*]])``
|
||||
|
||||
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
|
||||
=====
|
||||
|
||||
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
|
||||
true and false branches.
|
||||
As :ref:`if <if>`, but the result of the test form is named ``it`` in
|
||||
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:
|
||||
@ -33,9 +45,17 @@ true and false branches.
|
||||
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:
|
||||
@ -43,10 +63,10 @@ Evaluate the form for each element in the list for side-effects.
|
||||
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
|
||||
``True``.
|
||||
As ``ap-each``, but the form ``pred`` is run before the body forms on
|
||||
each iteration, and the loop ends if ``pred`` is false.
|
||||
|
||||
.. code-block:: hy
|
||||
|
||||
@ -60,11 +80,10 @@ Evaluate the form for each element where the predicate form returns
|
||||
ap-map
|
||||
======
|
||||
|
||||
Usage: ``(ap-map form list)``
|
||||
Usage: ``(ap-map form xs)``
|
||||
|
||||
The anaphoric form of map works just like regular map except that
|
||||
instead of a function object it takes a Hy form. The special name
|
||||
``it`` is bound to the current object from the list in the iteration.
|
||||
Create a generator like :py:func:`map` that yields each result of ``form``
|
||||
evaluated with ``it`` bound to successive elements of ``xs``.
|
||||
|
||||
.. code-block:: hy
|
||||
|
||||
@ -77,10 +96,12 @@ instead of a function object it takes a Hy form. The special name
|
||||
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
|
||||
determin when to apply the form.
|
||||
As ``ap-map``, but the predicate function ``predfn`` (yes, that's a
|
||||
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
|
||||
|
||||
@ -96,11 +117,9 @@ determin when to apply the form.
|
||||
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
|
||||
filter the elements of the list. The special name ``it`` is bound to
|
||||
the current element in the iteration.
|
||||
The :py:func:`filter` equivalent of ``ap-map``.
|
||||
|
||||
.. code-block:: hy
|
||||
|
||||
@ -113,11 +132,9 @@ the current element in the iteration.
|
||||
ap-reject
|
||||
=========
|
||||
|
||||
Usage: ``(ap-reject form list)``
|
||||
Usage: ``(ap-reject form xs)``
|
||||
|
||||
This function does the opposite of ``ap-filter``, it rejects the
|
||||
elements passing the predicate . The special name ``it`` is bound to
|
||||
the current element in the iteration.
|
||||
Equivalent to ``(ap-filter (not form) xs)``.
|
||||
|
||||
.. code-block:: hy
|
||||
|
||||
@ -130,10 +147,9 @@ the current element in the iteration.
|
||||
ap-dotimes
|
||||
==========
|
||||
|
||||
Usage ``(ap-dotimes n body)``
|
||||
Usage: ``(ap-dotimes n body…)``
|
||||
|
||||
This function evaluates the body *n* times, with the special
|
||||
variable ``it`` bound from *0* to *1-n*. It is useful for side-effects.
|
||||
Equivalent to ``(ap-each (range n) body…)``.
|
||||
|
||||
.. code-block:: hy
|
||||
|
||||
@ -148,15 +164,15 @@ variable ``it`` bound from *0* to *1-n*. It is useful for side-effects.
|
||||
ap-first
|
||||
========
|
||||
|
||||
Usage ``(ap-first predfn list)``
|
||||
Usage: ``(ap-first form xs)``
|
||||
|
||||
This function returns the first element that passes the predicate or
|
||||
``None``, with the special variable ``it`` bound to the current element in
|
||||
iteration.
|
||||
Evaluate the predicate ``form`` for each element ``it`` of ``xs``. When
|
||||
the predicate is true, stop and return ``it``. If the predicate is never
|
||||
true, return ``None``.
|
||||
|
||||
.. code-block:: hy
|
||||
|
||||
=>(ap-first (> it 5) (range 10))
|
||||
=> (ap-first (> it 5) (range 10))
|
||||
6
|
||||
|
||||
|
||||
@ -165,15 +181,15 @@ iteration.
|
||||
ap-last
|
||||
========
|
||||
|
||||
Usage ``(ap-last predfn list)``
|
||||
Usage: ``(ap-last form list)``
|
||||
|
||||
This function returns the last element that passes the predicate or
|
||||
``None``, with the special variable ``it`` bound to the current element in
|
||||
iteration.
|
||||
Evaluate the predicate ``form`` for every element ``it`` of ``xs``.
|
||||
Return the last element for which the predicate is true, or ``None`` if
|
||||
there is no such element.
|
||||
|
||||
.. code-block:: hy
|
||||
|
||||
=>(ap-last (> it 5) (range 10))
|
||||
=> (ap-last (> it 5) (range 10))
|
||||
9
|
||||
|
||||
|
||||
@ -182,18 +198,24 @@ iteration.
|
||||
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
|
||||
elements in the body and applying the result and the 3rd element
|
||||
etc. until the list is exhausted. Optionally an initial value can be
|
||||
supplied so the function will be applied to initial value and the
|
||||
first element instead. This exposes the element being iterated as
|
||||
``it`` and the current accumulated value as ``acc``.
|
||||
This macro is an anaphoric version of :py:func:`reduce`. It works as
|
||||
follows:
|
||||
|
||||
- Bind ``acc`` to the first element of ``xs``, bind ``it`` to the
|
||||
second, and evaluate ``form``.
|
||||
- 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
|
||||
|
||||
=>(ap-reduce (+ it acc) (range 10))
|
||||
=> (ap-reduce (+ it acc) (range 10))
|
||||
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.
|
||||
|
||||
|
@ -6,112 +6,71 @@
|
||||
;;; These macros make writing functional programs more concise
|
||||
|
||||
(defmacro ap-if [test-form then-form &optional else-form]
|
||||
`(do
|
||||
(rit `(do
|
||||
(setv it ~test-form)
|
||||
(if it ~then-form ~else-form)))
|
||||
(if it ~then-form ~else-form))))
|
||||
|
||||
|
||||
(defmacro ap-each [lst &rest body]
|
||||
"Evaluate the body form for each element in the list."
|
||||
`(for [it ~lst] ~@body))
|
||||
(defmacro ap-each [xs &rest body]
|
||||
(rit `(for [it ~xs] ~@body)))
|
||||
|
||||
|
||||
(defmacro ap-each-while [lst form &rest body]
|
||||
"Evaluate the body form for each element in the list while the
|
||||
predicate form evaluates to True."
|
||||
(setv p (gensym))
|
||||
`(do
|
||||
(defn ~p [it] ~form)
|
||||
(for [it ~lst]
|
||||
(if (~p it)
|
||||
~@body
|
||||
(break)))))
|
||||
(defmacro ap-each-while [xs form &rest body]
|
||||
(rit `(for [it ~xs]
|
||||
(unless ~form
|
||||
(break))
|
||||
~@body)))
|
||||
|
||||
|
||||
(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))))))
|
||||
(defmacro ap-map [form xs]
|
||||
(rit `(gfor it ~xs ~form)))
|
||||
|
||||
|
||||
(defmacro ap-map-when [predfn rep lst]
|
||||
"Yield elements evaluated for each element in the list when the
|
||||
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-map-when [predfn rep xs]
|
||||
(rit `(gfor it ~xs (if (~predfn it) ~rep 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))))))
|
||||
(defmacro ap-filter [form xs]
|
||||
(rit `(gfor it ~xs :if ~form it)))
|
||||
|
||||
|
||||
(defmacro ap-reject [form lst]
|
||||
"Yield elements returned when the predicate form evaluates to False"
|
||||
`(ap-filter (not ~form) ~lst))
|
||||
(defmacro ap-reject [form xs]
|
||||
(rit `(gfor it ~xs :if (not ~form) it)))
|
||||
|
||||
|
||||
(defmacro ap-dotimes [n &rest body]
|
||||
"Execute body for side effects `n' times, with it bound from 0 to n-1"
|
||||
(unless (numeric? n)
|
||||
(raise (TypeError (.format "{!r} is not a number" n))))
|
||||
`(ap-each (range ~n) ~@body))
|
||||
(rit `(for [it (range ~n)]
|
||||
~@body)))
|
||||
|
||||
|
||||
(defmacro ap-first [predfn lst]
|
||||
"Yield the first element that passes `predfn`"
|
||||
(with-gensyms [n]
|
||||
`(do
|
||||
(setv ~n None)
|
||||
(ap-each ~lst (when ~predfn (setv ~n it) (break)))
|
||||
~n)))
|
||||
(defmacro ap-first [form xs]
|
||||
(rit `(next
|
||||
(gfor it ~xs :if ~form it)
|
||||
None)))
|
||||
|
||||
|
||||
(defmacro ap-last [predfn lst]
|
||||
"Yield the last element that passes `predfn`"
|
||||
(with-gensyms [n]
|
||||
`(do
|
||||
(setv ~n None)
|
||||
(ap-each ~lst (none? ~n)
|
||||
(when ~predfn
|
||||
(setv ~n it)))
|
||||
~n)))
|
||||
(defmacro ap-last [form xs]
|
||||
(setv x (gensym))
|
||||
(rit `(do
|
||||
(setv ~x None)
|
||||
(for [it ~xs :if ~form]
|
||||
(setv ~x it))
|
||||
~x)))
|
||||
|
||||
|
||||
(defmacro! ap-reduce [form o!lst &optional [initial-value None]]
|
||||
"Anaphoric form of reduce, `acc' and `it' can be used for a form"
|
||||
`(do
|
||||
(defmacro! ap-reduce [form o!xs &optional [initial-value None]]
|
||||
(recur-sym-replace {'it (gensym) 'acc (gensym)} `(do
|
||||
(setv acc ~(if (none? initial-value)
|
||||
`(do
|
||||
(setv ~g!lst (iter ~g!lst))
|
||||
(next ~g!lst))
|
||||
(setv ~g!xs (iter ~g!xs))
|
||||
(next ~g!xs))
|
||||
initial-value))
|
||||
(ap-each ~g!lst (setv acc ~form))
|
||||
acc))
|
||||
(for [it ~g!xs]
|
||||
(setv acc ~form))
|
||||
acc)))
|
||||
|
||||
|
||||
(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])
|
||||
:if (and (symbol? a)
|
||||
(.startswith a '%))
|
||||
@ -132,3 +91,23 @@
|
||||
'(&kwargs %**))]
|
||||
~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 []
|
||||
(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 []
|
||||
(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])))
|
||||
|
||||
(defn test-ap-each-while []
|
||||
@ -47,7 +53,13 @@
|
||||
(assert (= (do (setv n []) (ap-dotimes 3 (.append n 3)) n)
|
||||
[3 3 3]))
|
||||
(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 []
|
||||
(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