Skip to content

Commit

Permalink
Add map-hyseq
Browse files Browse the repository at this point in the history
  • Loading branch information
Kodiologist committed Nov 9, 2024
1 parent 84564ef commit 2a273ee
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 16 deletions.
4 changes: 4 additions & 0 deletions NEWS.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
Unreleased
======================================================

New Features
------------------------------
* New macro `map-hyseq`.

Bug Fixes
------------------------------
* `map-model` now calls `as-model` only once (before its own recursion),
Expand Down
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ API
.. hy:automacro:: defmacro-kwargs
.. hy:automacro:: defmacro!
.. hy:autofunction:: macroexpand-all
.. hy:autofunction:: map-hyseq
.. hy:autofunction:: map-model
.. hy:autofunction:: match-fn-params
.. hy:automacro:: with-gensyms
Expand Down
43 changes: 28 additions & 15 deletions hyrule/macrotools.hy
Original file line number Diff line number Diff line change
Expand Up @@ -270,21 +270,34 @@
(_map-model (hy.as-model x) f))

(defn _map-model [x f]
(cond
(is-not (setx value (f x)) None)
(hy.as-model value)
(isinstance x hy.models.Sequence)
((type x)
(gfor elem x (_map-model elem f))
#** (cond
(isinstance x hy.models.FString)
{"brackets" x.brackets}
(isinstance x hy.models.FComponent)
{"conversion" x.conversion}
True
{}))
True
x))
(if (is-not (setx value (f x)) None)
(hy.as-model value)
(map-hyseq x (fn [contents]
(gfor elem contents (_map-model elem f))))))

(defn map-hyseq [x f]

"Apply the function ``f`` to the contents of the :ref:`sequential model <hy:hysequence>` ``x`` gathered into a tuple. ``f`` should return an iterable object. This result is then wrapped in the original model type, preserving attributes such as the brackets of an :class:`hy.models.FString`. ::
(map-hyseq '[:a :b :c] (fn [x]
(gfor e x (hy.models.Keyword (.upper e.name)))))
; => '[:A :B :C]
Unlike :hy:func:`map-model`, ``map-hyseq`` isn't inherently recursive.
If ``x`` isn't a sequential Hy model, it's returned as-is, without calling ``f``."

(if (isinstance x hy.models.Sequence)
((type x)
(f (tuple x))
#** (cond
(isinstance x hy.models.FString)
{"brackets" x.brackets}
(isinstance x hy.models.FComponent)
{"conversion" x.conversion}
True
{}))
x))


(defmacro with-gensyms [args #* body]
Expand Down
38 changes: 37 additions & 1 deletion tests/test_macrotools.hy
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
:readers [/])
(import
pytest
hyrule [macroexpand-all map-model match-fn-params])
hyrule [macroexpand-all map-hyseq map-model match-fn-params])


(defn test-defmacro-kwargs []
Expand Down Expand Up @@ -110,6 +110,42 @@
'(setv blah 1))))


(defn test-map-hyseq []

; If `x` isn't sequential (or not a model at all), `f` isn't called.
(assert (=
(map-hyseq
3
(fn [x] (raise ValueError)))
3))
(assert (=
(map-hyseq
[1 2]
(fn [x] (raise ValueError)))
[1 2]))

; `f` can be called when `x` is empty.
(assert (=
(map-hyseq
'[]
(fn [x] ['4]))
'[4]))

; `f` gets the sequence contents as a tuple.
(setv saw None)
(assert (=
(map-hyseq
'{1 2 3 4}
(fn [x]
(nonlocal saw)
(setv saw x)
(gfor e x (hy.models.Integer (+ e 1)))))
'{2 3 4 5}))
(assert (= saw #('1 '2 '3 '4))))
; Preservation of sequence attributes is tested as part of testing
; `map-model`.


(defn test-map-model []

; When the callback returns `None`, the element is recursed into, or
Expand Down

0 comments on commit 2a273ee

Please sign in to comment.