specter

Latest version: 1.1.3
2018-04-03T09:57:46.000370Z

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.

2018-04-03T12:14:27.000256Z

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

2018-04-03T12:14:55.000125Z

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

2018-04-03T12:15:38.000499Z

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

2018-04-03T12:17:28.000305Z

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

nathanmarz 2018-04-03T12:58:42.000644Z

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

nathanmarz 2018-04-03T13:01:25.000386Z

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)

2018-04-03T13:12:21.000007Z

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

nathanmarz 2018-04-03T13:24:31.000345Z

@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