Fork me on GitHub
#specter
<
2016-07-28
>
shader13:07:59

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

shader13:07:34

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

codonnell13:07:37

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

codonnell13:07:10

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

shader13:07:15

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

shader13:07:30

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

codonnell13:07:15

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

shader13:07:34

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)

codonnell13: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]?

shader13:07:49

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

codonnell13:07:06

should be able to do it with declarepath and providepath

codonnell13: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}

shader13:07:46

interesting

codonnell13:07:08

dammit, keep hitting enter instead of ctrl + enter

shader14:07:56

@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))

codonnell14: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.

nathanmarz19:07:19

@codonnell: good idea, merged it in

kenny19:07:58

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/cond-path
                   vector?
                   [rs/ALL MapWalker]
                   map?
                   [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?

nathanmarz20:07:09

@kenny: easiest is to use walker

nathanmarz20:07:14

(declarepath MapWalker)
(providepath MapWalker
  (stay-then-continue
    MAP-VALS
    (walker map?)
    MapWalker
    ))

kenny20:07:52

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

nathanmarz20:07:53

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

nathanmarz20:07:56

(declarepath MapWalker)
(providepath MapWalker
   (cond-path
     vector?
     [ALL MapWalker]
     map?
     (stay-then-continue MAP-VALS MapWalker)))

codonnell20: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.

kenny20:07:28

Ah, and stay-then-continue solves that problem

codonnell20: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

kenny20:07:21

Agreed, I definitely like @nathanmarz solution much better.

kenny20:07:42

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.

nathanmarz20:07:53

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

kenny20:07:10

Good to know 🙂

kenny20:07:54

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.

nathanmarz20:07:02

the nature of abstraction I think

nathanmarz20:07:15

like how every clojure function could be written manually in bytecode

kenny20:07:20

Or rather.. > the nature of a good abstraction

richiardiandrea20:07:58

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