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.@marcus165 not sure if it's the most idiomatic way, but you can use collect
/ collect-one
to collect the keys on the way.
You can even use a recursive path here to climb down arbitrary nested maps
(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)
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
@marcus165 @drowsy yes, value collection is the idiomatic way to do that
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)
Thank you @drowsy and @nathanmarz - that helped; I am now both enlightened and unblocked.
@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