Fork me on GitHub
#specter
<
2017-10-17
>
borkdude14:10:07

This code does what I want:

(def data (let [inner-data [{:bar #{:a}}
                            {:bar #{:a :b}}
                            {:bar #{:c}}]]
            {:a inner-data
             :b inner-data}))

(setval
 [(multi-path :a :b)
  ALL
  (multi-path
   [:bar (selected? ALL
                    (comp not #{:a :b}))]
   empty?)]
 NONE
 data)
but not in the case of nil or {}, then it adds keys with nil

nathanmarz14:10:30

@borkdude nil or {} for what?

nathanmarz14:10:34

the inner maps?

borkdude14:10:45

the result, {:a nil, :b nil}

nathanmarz15:10:39

not following

borkdude15:10:57

(setval
 [(multi-path :a :b)
  ALL
  (multi-path
   [:bar (selected? ALL
                    (comp not #{:a :b}))]
   empty?)]
 NONE
 nil)

borkdude15:10:14

;;=> {:a nil, :b nil}

nathanmarz15:10:21

you can use (multi-path (must :a) (must :b))

borkdude15:10:41

ah, a new construct… going to the wiki 🙂

borkdude15:10:23

when do you not want the behavior of must?

borkdude15:10:16

I mean, why is it not the default

nathanmarz15:10:23

i've found the need to definitely navigate to a key is far more common

nathanmarz15:10:25

and it's faster

nathanmarz15:10:55

also (update {} :a identity) ;; => {:a nil}

borkdude16:10:32

Still I find the code incredibly hard to read compared to normal Clojure…

nathanmarz16:10:48

Specter 1.0.4 released

borkdude16:10:21

Congrats on the new release

jeaye16:10:50

@nathanmarz Do you recommend using specter in lieu of clojure.core fns always? Or perhaps only when either 1) it's known to be faster; 2) it provides a much more robust solution;

jeaye16:10:24

Based on the benchmarks, it looks like specter isn't always faster. On a team, this makes it harder to bring in, since one can't just say "We're using specter for data access now; it's faster and more robust." Unfortunately, the ruleset of when to use it (i.e. get-in seems slow, transform is very fast, unless you're doing a mapv) is complex, then makes it much more expensive to adopt and learn.

nathanmarz17:10:27

@jeaye I use it always when it's more elegant than trying to do it in vanilla Clojure

nathanmarz17:10:40

the overhead is generally quite small even for cases where the vanilla Clojure is just as elegant

nathanmarz17:10:59

the notable exception is (select-any [:a :b :c] data) vs. (-> data :a :b :c)

nathanmarz17:10:56

I do believe it's possible to eliminate most of the overhead for that with additional work on Specter

nathanmarz17:10:41

though for a case like that I would do the latter expression in my own code since it's elegant

jeaye17:10:23

Word. Thanks for the info and congrats on the 1.0.4 release.

tanzoniteblack17:10:59

is there a way to use specter to select only distinct values (besides the obvious of (distinct (specter/select apath structure)) which requires looping through the final result)

tanzoniteblack17:10:54

(not that this is really a huge performance hit, but just curious)

nathanmarz17:10:37

@tanzoniteblack you can do (into #{} (traverse apath structure))

nathanmarz17:10:43

that's the fastest

tanzoniteblack17:10:55

not the same though, since set's don't retain the order (which is important in this case)

tanzoniteblack17:10:38

(which I didn't specify in my question 🙂 , but distinct does retain the order, which is a useful property in my case)

nathanmarz17:10:37

distinct is the easiest, though traverse just returns a reducible so you can do it faster if needed

nathanmarz17:10:02

reduce over the traverse return to add into a vector while keeping a mutable set in scope in order to not append duplicates

nathanmarz17:10:31

actually (into [] (distinct) (traverse apath structure)) might be equivalent to that

nathanmarz17:10:37

yea it is:

user=> (into [] (distinct) (traverse ALL [1 2 3 4 5 2 1 7]))
[1 2 3 4 5 7]

tanzoniteblack17:10:16

nice to know I can use traverse with transducers

nathanmarz17:10:50

you can also make a specter-based transducer with traverse-all

borkdude19:10:18

@nathanmarz When you do a multi-path over an ALL, will it iterate multiple times over the sequence? My code now looks like this:

(setval
      [(multi-path (must :report) (must :highlights))
       ALL
       (multi-path
        [:pico
         #(not (some (set picos) %))]
        ;; if key is removed, remove the map entirely
        (comp not :pico))]
      NONE
      results)

borkdude19:10:55

but I could have used just a filter/remove function on the ALL

borkdude19:10:10

I could imagine that specter fuses these operations

nathanmarz19:10:05

that does not iterate over any sequence multiple times

borkdude19:10:10

I think this is more readable though:

(transform
      [(multi-path (must :report) (must :highlights))]
      #(remove
        (fn [m]
          (not (some (set picos) (:pico m)))) %)
      results)

borkdude19:10:26

but good to know it doesn’t iterate multiple times

nathanmarz19:10:25

@borkdude the specter version can be done much simpler:

(setval
  [(multi-path (must :report) (must :highlights))
   ALL
   (selected? :pico ALL (pred (set picos)))]
  NONE
  results)

borkdude19:10:19

The problem is that I need something slightly different:

(def picos [:a :b])
(def results {:highlights [{:pico [:a]}    ;; keep
                           {:pico [:a :b]} ;; keep
                           {:pico [:c]}    ;; drop
                           {:pico [:c :d]} ;; drop
                           {:pico [:c :a]}]}) ;;keep

(transform
 [(multi-path (must :report) (must :highlights))]
 #(remove
   (fn [m]
     (not (some (set picos) (:pico m)))) %)
 results) ;;=> {:highlights ({:pico [:a]} {:pico [:a :b]} {:pico [:c :a]})}

borkdude19:10:00

so something like (selected? :pico ANY (pred (comp not (set picos))))

borkdude19:10:09

but I didn’t find something like ANY

nathanmarz19:10:27

ah you just need (not-selected? ALL (set picos))

nathanmarz19:10:55

btw, the pred is only there for performance, and is only needed if picos is a local variable

borkdude19:10:50

in my original code it is a local

nathanmarz19:10:43

the specter version will also preserve the sequence type

borkdude20:10:21

yes, that’s a bonus

nathanmarz20:10:44

also will only convert picos to a set once, instead of for every element

borkdude20:10:15

What is the explanation of this result? I don’t understand selected? and not-selected? well enough. (select [(selected? ALL odd?)] [1 2 3]) ;;=> [[1 2 3]]

nathanmarz20:10:03

selected? stays navigated if the path selects at least one value

nathanmarz20:10:25

in this case it's run on the root, and it is true

nathanmarz20:10:38

so it selects the root [1 2 3]

borkdude20:10:45

ah, that’s it

borkdude20:10:13

so with a local I need (pred (set picos)) and then it will substitute the value of the expression?

nathanmarz20:10:21

the pred in that case avoids a runtime conversion from an implicit navigator (a set) to a navigator

nathanmarz20:10:32

sets are implicitly wrapped in pred, but if the value at a spot is dynamic, then specter cannot know that until runtime

nathanmarz20:10:16

it's not a huge deal, but it is a protocol invocation