Use a gensym for it in anaphoric macros

This commit is contained in:
Kodi Arfer 2019-12-21 13:26:37 -05:00
parent bafd919605
commit e57bbb92db
3 changed files with 50 additions and 18 deletions

View File

@ -17,6 +17,12 @@ 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

View File

@ -6,72 +6,72 @@
;;; 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 [lst &rest body]
"Evaluate the body form for each element in the list." "Evaluate the body form for each element in the list."
`(for [it ~lst] ~@body)) (rit `(for [it ~lst] ~@body)))
(defmacro ap-each-while [lst form &rest body] (defmacro ap-each-while [lst form &rest body]
"Evaluate the body form for each element in the list while the "Evaluate the body form for each element in the list while the
predicate form evaluates to True." predicate form evaluates to True."
`(for [it ~lst] (rit `(for [it ~lst]
(unless ~form (unless ~form
(break)) (break))
~@body)) ~@body)))
(defmacro ap-map [form lst] (defmacro ap-map [form lst]
"Yield elements evaluated in the form for each element in the list." "Yield elements evaluated in the form for each element in the list."
`(gfor it ~lst ~form)) (rit `(gfor it ~lst ~form)))
(defmacro ap-map-when [predfn rep lst] (defmacro ap-map-when [predfn rep lst]
"Yield elements evaluated for each element in the list when the "Yield elements evaluated for each element in the list when the
predicate function returns True." predicate function returns True."
`(gfor it ~lst (if (~predfn it) ~rep it))) (rit `(gfor it ~lst (if (~predfn it) ~rep it))))
(defmacro ap-filter [form lst] (defmacro ap-filter [form lst]
"Yield elements returned when the predicate form evaluates to True." "Yield elements returned when the predicate form evaluates to True."
`(gfor it ~lst :if ~form it)) (rit `(gfor it ~lst :if ~form it)))
(defmacro ap-reject [form lst] (defmacro ap-reject [form lst]
"Yield elements returned when the predicate form evaluates to False" "Yield elements returned when the predicate form evaluates to False"
`(gfor it ~lst :if (not ~form) it)) (rit `(gfor it ~lst :if (not ~form) it)))
(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" "Execute body for side effects `n' times, with it bound from 0 to n-1"
`(for [it (range ~n)] (rit `(for [it (range ~n)]
~@body)) ~@body)))
(defmacro ap-first [predfn lst] (defmacro ap-first [predfn lst]
"Yield the first element that passes `predfn`" "Yield the first element that passes `predfn`"
`(next (rit `(next
(gfor it ~lst :if ~predfn it) (gfor it ~lst :if ~predfn it)
None)) None)))
(defmacro ap-last [predfn lst] (defmacro ap-last [predfn lst]
"Yield the last element that passes `predfn`" "Yield the last element that passes `predfn`"
(setv x (gensym)) (setv x (gensym))
`(do (rit `(do
(setv ~x None) (setv ~x None)
(for [it ~lst :if ~predfn] (for [it ~lst :if ~predfn]
(setv ~x it)) (setv ~x it))
~x)) ~x)))
(defmacro! ap-reduce [form o!lst &optional [initial-value None]] (defmacro! ap-reduce [form o!lst &optional [initial-value None]]
"Anaphoric form of reduce, `acc' and `it' can be used for a form" "Anaphoric form of reduce, `acc' and `it' can be used for a form"
`(do (recur-sym-replace {'it (gensym) 'acc (gensym)} `(do
(setv acc ~(if (none? initial-value) (setv acc ~(if (none? initial-value)
`(do `(do
(setv ~g!lst (iter ~g!lst)) (setv ~g!lst (iter ~g!lst))
@ -79,7 +79,7 @@
initial-value)) initial-value))
(for [it ~g!lst] (for [it ~g!lst]
(setv acc ~form)) (setv acc ~form))
acc)) acc)))
(deftag % [expr] (deftag % [expr]
@ -111,3 +111,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))

View File

@ -7,7 +7,13 @@
(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 [])