Fork me on GitHub
#specter
<
2024-01-21
>
Matthew DiLoreto18:01:47

I'm wondering if there is a more idiomatic way to accomplish the following transformation: I have a vector of maps that each contain a vector of children:

(def a-structure [{:children [{:a 1 :b 2} {:a 3 :b 4}] :something "a"}
                   {:children [{:a 4 :b 5} {:a 6 :b 10}] :something "b"}])
I want to transform the parent maps, and also filter and transform each of the child maps. I came up with the following, where I do the filter and transform of children in two separate paths:
(sp/multi-transform
 [sp/ALL (sp/multi-path
          [(sp/terminal (fn [thing-with-children]
                          {:new-thing 1
                           :something-renamed (:something thing-with-children)
                           :children (:children thing-with-children)}))]  ; apply transform to top-level object
          [:children (sp/filterer [(sp/must :a) (sp/pred<= 1)]) (sp/terminal sp/NONE)] ; remove duds
          [:children
           sp/ALL
           (sp/terminal (fn [child] {:a-renamed (:a child) :b-renamed (:b child)}))])] ; apply transform to each non-filtered child
 a-structure)
Ignoring the fact that I'm just renaming keys, how can I make this transformation better? I feel like the second path replacing filtered children with NONE can't be the right way, but I can't figure out how to merge it with the last path, that actually transforms the unfiltered children.

Matthew DiLoreto18:01:38

I tried inlining the filter:

(sp/multi-transform
 [sp/ALL (sp/multi-path
          [(sp/terminal (fn [thing-with-children]
                          {:new-thing 1
                           :something-renamed (:something thing-with-children)
                           :children (:children thing-with-children)}))]  ; apply transform to top-level object
          [:children
           (sp/filterer [(sp/must :a) (sp/pred> 1)]) ; remove duds
           sp/ALL
           (sp/terminal (fn [child] {:a-renamed (:a child) :b-renamed (:b child)}))])] ; apply transform to each non-filtered child
 a-structure)
But the :children vector still contains the "filtered" item, because the first path includes :children in the top-level result
[{:new-thing 1,
  :something-renamed "a",
  :children [{:a 1, :b 2} {:a-renamed 3, :b-renamed 4}]}
 {:new-thing 1,
  :something-renamed "c",
  :children [{:a-renamed 4, :b-renamed 5} {:a-renamed 6, :b-renamed 10}]}]
(notice the first child in the first result is not-transformed)

nathanmarz20:01:22

map-key is the navigator to use for renaming keys

Matthew DiLoreto20:01:35

Thanks! What do you think about

[:children (sp/filterer [(sp/must :a) (sp/pred<= 1)]) (sp/terminal sp/NONE)]
It seems like "select things to filter, replace them with NONE so they are omitted", which feels redundant

nathanmarz20:01:19

you need terminal-val there

nathanmarz20:01:22

[:children ALL (sp/selected? (sp/must :a) (sp/pred<= 1)) (sp/terminal-val sp/NONE)] is more performant

gratitude-thank-you 1