Fork me on GitHub
#specter
<
2020-06-23
>
Casey13:06:14

hey folks, given a vector of vectors of integers, and a separate vector of integers to remove, how can I remove all integers from the sub-vectors that are in the separate vector?

(def to-remove [ 4 5 6 ])
(def coll [ [1 2 4] [3 5 8 ] [6] [] ])
(specter/transform [(specter/filterer not-empty) specter/ALL ..?.. ] specter/NONE coll)

;; => [ [1 2] [3 8] [] [] ]

Casey13:06:54

Seems like this works, is there a more efficient way to write it?

(specter/setval [specter/ALL specter/ALL (fn [val]  (some #(= val %) to-remove))] specter/NONE coll)

jsa-aerial17:06:47

Might be nicer if you made to-remove a set, then it is just (to-remove val) or replace fn form with #(to-remove %)

avi18:06:58

Hi, sorry if this is a FAQ, but how can I navigate to a map, and then filter the entries in the map, based on certain nested values in the values?

avi18:06:23

Here’s what I’m doing in “plain old Clojure” (plus Medley) that I’m curious how to translate to Specter:

(->> (db/read "db")
     (:technologies)
     (medley/filter-vals
       (fn [tech] (some (fn [rec] (rec "ratified"))
                        (get tech "recommendations")))))

phronmophobic18:06:22

not sure it's the most straightforward approach, but you could probably use:

[:technology MAP-VALS]
for the path. in your transform function: • return the original value to keep it • return spec/NONE to remove a value

avi18:06:48

Thanks! But I’m not looking to transform, just to query.

avi18:06:32

Yeah, I left if off but when I ran the above code, the last form in the thread-last form was keys

phronmophobic18:06:41

[:technology MAP-VALS (fn [tech] (some (fn [rec] (rec "ratified"))
                        (get tech "recommendations")))]

avi18:06:01

interesting, I thought I tried that… I’ll try again, brb!

phronmophobic18:06:33

i'm not specter expert either ¯\(ツ)

avi18:06:09

Wait… that’ll navigate to the results of the predicate, right? But I want the map under :technologies, just filtered

avi18:06:21

I’m very new to Specter but I’d think it’d have something roughly equivalent to Medley’s filter-vals (which filters a map by applying a predicate to the vals in the map, and returns a map containing the matching entries)

avi18:06:45

(Or something equivalent to filter that I could use to the same effect)

avi18:06:44

Right! I tried that but had trouble with it. Thought maybe I just didn’t understand it. I’ll try again… brb

phronmophobic18:06:12

imo, specter is much more useful if you're transforming or setting data in a deeply nested data structure. if you're just drilling into a nested data structure, I usually just use the normal clojure functions

avi18:06:32

that makes sense.

avi18:06:54

But I’d like to build up my own intuition as to when to use Specter, by seeing what it’s like to use it in cases like this

avi18:06:46

Also, I had to walk a Clojure newbie through the above code and it was non-trivial to explain. I think the path concept could maybe be much more straightforward for folks new to Clojure and Lisps.

phronmophobic18:06:13

i'm only a specter novice, so there's probably a very straightforward way to do it that I don't know about

avi18:06:21

I hear ya, I have the same suspicion

avi18:06:58

Trying this:

(select [:technologies
         (filterer MAP-VALS "recommendations" ALL "ratified")
         MAP-KEYS]
        db)
getting: java.lang.ClassCastException: "class java.lang.String cannot be cast to class java.util.Map$Entry…

phronmophobic18:06:43

specter doesn't recognize strings as map keys like it does for keywords

phronmophobic18:06:01

I think "recommendations" needs to be (keypath "recommendations")

avi18:06:04

I thought i saw that work, one sec…

phronmophobic18:06:39

hmmm, maybe it does

avi18:06:46

Yeah, this works: (select [:technologies MAP-VALS "recommendations" ALL] db)

👍 3
phronmophobic18:06:43

filterer is complaining because it's turning the map into a sequence

phronmophobic18:06:50

maybe something like:

(spec/select [:technologies
              (spec/filterer  (spec/nthpath 1) "recommendations" spec/ALL "ratified")
              ALL
              (spec/nthpath 0)]
        db)

avi18:06:47

huh, that’s surprising. I wonder why it’s doing that. I’d think that’d be antithetical to Specter’s general behavior of leaving types as-is

avi18:06:58

also: thank you!

avi18:06:33

interesting… this “works”, but the result is incorrect:

(select [:technologies
         (filterer (nthpath 1) "recommendations" ALL "ratified")
         MAP-KEYS]
        db)

phronmophobic18:06:42

> with the path yields anything other than an empty sequence.

phronmophobic18:06:48

the path is probably yielding false

avi18:06:04

the path in filterer ?

avi18:06:12

I wouldn’t think so…?

avi18:06:18

why would it yield false?

avi18:06:28

I thought any path to something that doesn’t exist yields nil?

phronmophobic18:06:29

or whatever is in the ratified key

phronmophobic18:06:52

does the ratified key exist even for unratified recommendations?

avi18:06:22

right. but most of the maps in the sequences that correspond to the "recommendations" keys do not have the key "ratified"; only a few do. I’m trying to find those technologies that have recommendations that have been ratified

phronmophobic18:06:17

this seems to work:

(def data {:technologies
           {0 {"recommendations" [{"ratified" true}]}
            1 {"recommendations" [{"ratified" false}]}
            2 {"recommendations" []}}})

(spec/select [:technologies

              (spec/filterer  (spec/nthpath 1) "recommendations" spec/ALL "ratified" identity)
              spec/MAP-KEYS
              ;; ALL
              ;;(spec/nthpath 0)
              ]
             data)

phronmophobic18:06:53

the above will return [0]

avi18:06:00

it does!

avi18:06:07

I don’t get it though 🙃

avi18:06:36

why is the identity needed? what is it doing?

phronmophobic18:06:27

so without identity and removing filterer:

(def data {:technologies
           {0 {"recommendations" [{"ratified" true}]}
            1 {"recommendations" [{"ratified" false}]}
            2 {"recommendations" []}}})

(spec/select [:technologies
              ALL
              (spec/nthpath 1) "recommendations" spec/ALL "ratified" 
              ;; spec/MAP-KEYS
              ;; ALL
              ;;(spec/nthpath 0)
              ]
             data)

phronmophobic18:06:36

you get [true false]

avi18:06:10

That’s not what I’m getting

phronmophobic18:06:10

basically, it can navigate to the "ratified" key

avi18:06:17

oh wait…

phronmophobic18:06:22

I also removed the filterer

avi18:06:29

ah interesting

phronmophobic18:06:33

and the path exists if it can navigate there

avi18:06:45

I see, you did that as a way to debug the filterer path?

👍 3
phronmophobic18:06:04

so filterer will keep paths that it can navigate to, even if the value it navigates to is falsey

avi18:06:06

cool, that’s clever, makes sense!

avi18:06:14

that’s… surprising

avi18:06:17

but, so be it

phronmophobic18:06:25

so identity is the simplest function that I can think of to filter based on truthiness

phronmophobic18:06:56

I think filterer has the right design, but it is a little surprising at first

phronmophobic18:06:49

(some? false) => true

phronmophobic19:06:15

actually, boolean is probably clearer

phronmophobic19:06:39

(def data {:technologies
           {0 {"recommendations" [{"ratified" true}]}
            1 {"recommendations" [{"ratified" false}]}
            2 {"recommendations" []}}})

(spec/select [:technologies
              (spec/filterer  (spec/nthpath 1) "recommendations" spec/ALL "ratified" boolean)
              spec/MAP-KEYS
              ]
             data)

phronmophobic19:06:37

some?'s doc string is correct:

clojure.core/some?
 [x]
Added in 1.6
  Returns true if x is not nil, false otherwise.

avi19:06:28

So some? is equivalent to (complement nil?) …. ?

👍 3
avi19:06:08

Anyway, I still don’t quite get filterer — but a prerequisite to getting it is seeing what works. Which I’ve got now. Thank you so much!

👍 3
Casey18:06:58

Is there away to setval on a path but only set the first matching value?

Casey18:06:25

Using my example from before, if coll was (def coll [ [1 2 4] [3 5 8 4] [6] [] ]) I'd like the result to be ;; => [ [1 2] [3 8 4] [] [] ] (that is, the 4 is only removed once)

Casey18:06:24

If I add a FIRST after the (fn ...) in the path, I get a Error: 4 is not ISeqable