Fork me on GitHub
#specter
<
2017-10-16
>
hkjels11:10:26

Is it possible to start a search from a trees leaves? Or select the deepest nested match only?

borkdude15:10:30

I have a data structure like {:foo [{:bar #{:a x}} {:bar #{:b c}]} and I want to filter :foo by some predicate. Now I’m using (update m :foo filter-fn) where filter-fn is something like (filter coll pred). How do I write this using Specter? Does it have some filter functionality?

nathanmarz15:10:19

@hkjels you could do something like that with zippers, but there's probably a better way

nathanmarz15:10:15

for deepest match, you could do select with putval at every recursive step, and then choose longest result at end

nathanmarz15:10:22

@borkdude that looks like (setval [:foo ALL remove-fn?] NONE data)

borkdude15:10:11

so setting NONE is semantically equivalent to removing an element in Specter

hkjels15:10:28

@nathanmarz I was overcomplicating it. I realized that I always need the fift level or deeper, so a simple predicate solved it

borkdude15:10:53

Maybe there should be a Specter course on Clojure Academy 🙂

borkdude16:10:29

Almost what I want:

(def data {:foo [{:bar [:a]}
                 {:bar [:a :b]}
                 {:bar [:c]}]})

(setval
 [:foo ALL :bar
  #(seq (set/intersection #{:a}
                          (set %)))]
 NONE
 data) ;;=> {:foo [{} {} {:bar [:c]}]}

borkdude16:10:34

but I want to get rid of the empty maps

borkdude16:10:42

This works:

(setval
 [:foo ALL
  #(seq (set/intersection #{:a}
                          (set (:bar %))))]
 NONE
 data)

hkjels16:10:56

More learning-material would be great. I saw one of your talks on Specter yesterday @nathanmarz and was really impressed by your presentation skills

borkdude16:10:21

actually it doesn’t work yet, hang on

borkdude16:10:51

the predicate should be reversed, so with empty? 🙂

borkdude16:10:38

How do I update two paths in parallel, e.g. {:foo 1 :bar 2}, (transform [#{:foo :bar} inc]), is this possible?

borkdude16:10:47

ah, multi-path

nathanmarz17:10:01

@borkdude for your empty map issue:

(setval
  [:foo
   ALL
   (multi-path
     [:bar (selected? ALL (pred= :a))]
     empty?)]
  NONE
  data)

nathanmarz17:10:37

the wiki is much better nowadays and there was a screencast released recently

nathanmarz17:10:43

linked on the readme

borkdude17:10:57

@nathanmarz If I understand correctly, the function empty? is a selector for the path :bar, after the right path has been applied, so it removes the empty map?

borkdude17:10:02

On the wiki it says: for transforms it applies in order

nathanmarz17:10:20

empty? is a filter run after removal of :bar values matching the first path

borkdude17:10:48

ah now I get it, I read it wrong, as if :bar and (selected? ....) were both paths, but empty? itself is a “path” on the value returned by the previous path

nathanmarz18:10:42

yea, that's right

nathanmarz18:10:16

that's also much more efficient than the set intersection approach

borkdude18:10:51

and because selected? stops the navigation, what is returned is the empty map, because no key-vals have been selected because pred= returned false

borkdude18:10:40

those are patterns worth remembering

nathanmarz18:10:52

well, selected? is run on the value of :bar

nathanmarz18:10:04

it selects any vectors that have :a in them

nathanmarz18:10:30

which is then set to NONE, removing the key/val pair from the map

nathanmarz18:10:42

then empty? is run, removing any maps from the vector that are now empty

borkdude18:10:03

well, I need to check for multiple values, so I need the intersection, like this:

(setval
 [:foo
  ALL
  (multi-path
   [:bar (selected? #(empty? (set/intersection #{:a :b} %)))]
   empty?)]
 NONE
 data)

nathanmarz18:10:12

you can do [:bar (selected? ALL #{:a :b})]

nathanmarz18:10:31

there's also no point to using selected? with only a function

nathanmarz18:10:47

sets are implicit filter predicates in specter

nathanmarz18:10:56

interpreted equivalent to a clojure function