Fork me on GitHub

how do you do a recursive traversal? I want to create a transform for all of the handlers in a bidi routing structure. This requires getting all of the 'leaves' of a nested table/vector structure, where 'leaf' is basically a map-val that is a keyword


the problem is that the leaves are at variable and unbounded depths in the tree

Chris O’Donnell13:07:37

@shader: The easiest way is to use walker, though sometimes you need to define a custom navigator.

Chris O’Donnell13:07:10

Another option would be to use the zipper navigators, though I haven't played with those personally.


I've tried walker, but I don't really know how to use it I guess


I don't know how to select for "only keywords that are values of maps"

Chris O’Donnell13:07:15

Could you give an example of what you're trying to do? I'm not familiar with bidi routing structures.


I'd like to get all all of :trials-list, :trial-details, etc., without getting the :id keywords in the vectors (those define url parameters, not handlers)

Chris O’Donnell13:07:16

So if you were traversing that data structure, you'd want to get a vector [:trials-list :trial-details :records-list :record-details :authorized-trials :revoke :messages :delete-message]?


well, specifically a view I can pass to transform to replace them with an actual handler function

Chris O’Donnell13:07:06

should be able to do it with declarepath and providepath

Chris O’Donnell13:07:20

=> (declarepath DEEP-MAP-VALS)
=> (providepath DEEP-MAP-VALS (sp/if-path map? [sp/MAP-VALS DEEP-MAP-VALS] sp/STAY))
=> (select DEEP-MAP-VALS {:a {:b 2} :c {:d 3 :e {:f 4}} :g 5})
[2 3 4 5]
=> (transform DEEP-MAP-VALS inc {:a {:b 2} :c {:d 3 :e {:f 4}} :g 5})
{:a {:b 3}, :c {:d 4, :e {:f 5}}, :g 6}



Chris O’Donnell13:07:08

dammit, keep hitting enter instead of ctrl + enter


@codonnell: thanks, that helped a lot. My final implementation is:

(providepath DEEP-MAP-VALS (cond-path map? [MAP-VALS DEEP-MAP-VALS] vector? [LAST DEEP-MAP-VALS] :default STAY))

Chris O’Donnell14:07:18

@nathanmarz: I added DEEP-MAP-VALS as an example to my copy of the wiki, since people ask about recursive navigators pretty often.


@codonnell: good idea, merged it in


I'm not sure I fully understand how to do a recursive transform. I want to change the value of the key :changeme to negative its current value. This should be done for every nested map inside the structure, including all maps inside a sequential collection. It seems like this would be super simple to do in specter but I am guessing I don't fully understand the way recursion works in specter. Here is what I wrote:

(rsm/declarepath MapWalker)
(rsm/providepath MapWalker
                   [rs/ALL MapWalker]
                   [rs/MAP-VALS coll? MapWalker]))

(rsm/transform [MapWalker :changeme]
               #(- %)
               {:changeme    1
                :ignore-this ""
                :foo2        {:changeme 2
                              :bar2     "something"}
                :foo3        [{:changeme 3}
                              {:changeme    3
                               :ignore-this ""}]})
Expected output:
{:changeme    -1
 :ignore-this ""
 :foo2        {:changeme -2
               :bar2     "something"}
 :foo3        [{:changeme -3}
               {:changeme    -3
                :ignore-this ""}]}
Any help?


@kenny: easiest is to use walker


(declarepath MapWalker)
(providepath MapWalker
    (walker map?)


That works. Why does the code I sent not work? It reads like it should work 😛


@kenny: you can modify it like this to make it work


(declarepath MapWalker)
(providepath MapWalker
     [ALL MapWalker]
     (stay-then-continue MAP-VALS MapWalker)))

Chris O’Donnell20:07:53

@kenny: I think the problem is you're not actually navigating to anything. You navigate deeper when you encounter a vector or a map, but you stop navigation when you encounter anything else, rather than staying there.


Ah, and stay-then-continue solves that problem

Chris O’Donnell20:07:24

Though as @nathanmarz neatly solved for your use case, you want to stay at each map, since you want to grab its value for :changeme


Agreed, I definitely like @nathanmarz solution much better.


In this case I guess the equivalent Clojure code wouldn't be all that bad to maintain anyways as I could have just used clojure.walk.


another thing to note is the second solution is significantly faster than using walker or clojure.walk manually


Good to know 🙂


It is interesting that each approach you take in Clojure has an "equivalent" approach in specter -- clojure.walk is to walker as manually writing a recursive function is to cond-path.


the nature of abstraction I think


like how every clojure function could be written manually in bytecode


Or rather.. > the nature of a good abstraction


Oh I think this declarepath feature is what I was looking for as well for recursive navigation...I notice that in case of unexpected nil sometimes on the path I am navigating, an exception is thrown...still need to investigate so I will come up with a better test case to submit here