Fork me on GitHub
#specter
<
2018-04-03
>
Marcus Pemer09:04:46

I came across an interesting cunundrum this weekend and my initial attempts to find answers in the documentation has come up short. Hopefully someone on this channel will have a better grasp on things than I do. Suppose I had a three-layer data structure of maps. I would like to run a transformation on the leaves, similar to this:

(transform [MAP-VALS MAP-VALS :b] str/capitalize
           {:top-key-a
            {:middle-key-aa {:a "a" :b "b" :c "c"}
             :middle-key-ab {:a "a" :b "b" :c "c"}}
            :top-key-b
            {:middle-key-ba {:a "x" :b "y" :c "z"}
             :middle-key-bb {:a "u" :b "v" :c "w"}}})
Which yields:
{:top-key-a
 {:middle-key-aa {:a "a", :b "B", :c "c"},
  :middle-key-ab {:a "a", :b "B", :c "c"}},
 :top-key-b
 {:middle-key-ba {:a "z", :b "Y", :c "z"},
  :middle-key-bb {:a "u", :b "V", :c "w"}}}
Now, suppose I want to run a slightly more complex transformation on the leaves. Instead of running str/capitalize, which only takes the element itself as input, I would like to run my own custom function that takes the element to be transformed along with the ancestor keys following the path to the element. In this case I would like my transformation function to take :top-key-n and :middle-key-xy. The actual invocations of my transform function would then look something like this:
(my-xform :top-key-a :middle-key-aa :b "b")
(my-xform :top-key-a :middle-key-ab :b "b")
(my-xform :top-key-b :middle-key-ba :b "y")
(my-xform :top-key-b :middle-key-bb :b "v")
Is there an idiomatic way to achieve this in Specter? Any advice appreciated.

drowsy12:04:27

@marcus165 not sure if it's the most idiomatic way, but you can use collect / collect-one to collect the keys on the way.

drowsy12:04:55

You can even use a recursive path here to climb down arbitrary nested maps

drowsy12:04:38

(def MAP-KV-NODES
  (recursive-path [] p
     (cond-path
       map? [ALL (collect-one FIRST) LAST p]
       :else STAY)))
(transform [MAP-KV-NODES] myx-form data)

drowsy12:04:28

slightly tricky part is, that you need to navigate to the key/value pairs of a map using ALL, collect the key with FIRST and navigate to its val with LAST

nathanmarz12:04:42

@marcus165 @drowsy yes, value collection is the idiomatic way to do that

nathanmarz13:04:25

can easily make a wrapper around [ALL (collect-one FIRST) LAST] so you can do (transform [MY-MAP-VALS MY-MAP-VALS MAP-VALS] (fn [top middle v] ...) data)

Marcus Pemer13:04:21

Thank you @drowsy and @nathanmarz - that helped; I am now both enlightened and unblocked.

nathanmarz13:04:31

@marcus784 take a look at "Value Collection" under https://github.com/nathanmarz/specter/wiki/Cheat-Sheet to see all the things you can do with collected values