specter

Latest version: 1.1.3
Casey 2020-06-23T13:27:14.072Z

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] [] [] ]

Casey 2020-06-23T13:39:54.073300Z

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-aerial 2020-06-23T17:06:47.074700Z

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

avi 2020-06-23T18:00:58.075800Z

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?

avi 2020-06-23T18:01:23.075900Z

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")))))

avi 2020-06-23T18:01:26.076100Z

Thanks!

phronmophobic 2020-06-23T18:04:22.076600Z

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

avi 2020-06-23T18:04:48.076800Z

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

phronmophobic 2020-06-23T18:05:06.077100Z

oh

avi 2020-06-23T18:05:32.077300Z

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

phronmophobic 2020-06-23T18:06:41.077500Z

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

avi 2020-06-23T18:07:01.077800Z

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

phronmophobic 2020-06-23T18:07:33.078Z

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

avi 2020-06-23T18:08:09.078400Z

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

avi 2020-06-23T18:10:21.078600Z

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)

avi 2020-06-23T18:10:45.078800Z

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

avi 2020-06-23T18:11:44.079200Z

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

phronmophobic 2020-06-23T18:12:12.079400Z

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

avi 2020-06-23T18:12:32.079700Z

that makes sense.

avi 2020-06-23T18:12:54.079900Z

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

phronmophobic 2020-06-23T18:13:20.080100Z

:thumbsup:

avi 2020-06-23T18:13:46.080300Z

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.

phronmophobic 2020-06-23T18:14:13.080500Z

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

avi 2020-06-23T18:17:21.080700Z

I hear ya, I have the same suspicion

avi 2020-06-23T18:17:58.080900Z

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…

phronmophobic 2020-06-23T18:18:43.081100Z

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

avi 2020-06-23T18:18:55.081300Z

really?

phronmophobic 2020-06-23T18:19:01.081500Z

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

avi 2020-06-23T18:19:04.081700Z

I thought i saw that work, one sec…

phronmophobic 2020-06-23T18:19:39.081900Z

hmmm, maybe it does

avi 2020-06-23T18:19:46.082100Z

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

👍 1
Casey 2020-06-23T18:29:58.083Z

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

Casey 2020-06-23T18:34:25.083900Z

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)

Casey 2020-06-23T18:35:24.084600Z

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

phronmophobic 2020-06-23T18:45:43.084700Z

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

phronmophobic 2020-06-23T18:45:50.084900Z

maybe something like:

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

avi 2020-06-23T18:46:47.085100Z

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

avi 2020-06-23T18:46:58.085300Z

also: thank you!

avi 2020-06-23T18:48:33.085500Z

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

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

phronmophobic 2020-06-23T18:51:08.085700Z

shoot

phronmophobic 2020-06-23T18:51:42.085900Z

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

phronmophobic 2020-06-23T18:51:48.086100Z

the path is probably yielding false

avi 2020-06-23T18:52:04.086300Z

the path in filterer ?

phronmophobic 2020-06-23T18:52:09.086500Z

right

avi 2020-06-23T18:52:12.086700Z

I wouldn’t think so…?

avi 2020-06-23T18:52:18.086900Z

why would it yield false?

avi 2020-06-23T18:52:28.087100Z

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

phronmophobic 2020-06-23T18:52:29.087300Z

or whatever is in the ratified key

phronmophobic 2020-06-23T18:52:52.087500Z

does the ratified key exist even for unratified recommendations?

avi 2020-06-23T18:53:22.087700Z

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

phronmophobic 2020-06-23T18:54:17.087900Z

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)

phronmophobic 2020-06-23T18:54:53.088100Z

the above will return [0]

avi 2020-06-23T18:55:00.088300Z

it does!

avi 2020-06-23T18:55:07.088500Z

I don’t get it though 🙃

avi 2020-06-23T18:55:36.088700Z

why is the identity needed? what is it doing?

phronmophobic 2020-06-23T18:56:27.088900Z

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)

phronmophobic 2020-06-23T18:56:36.089100Z

you get [true false]

avi 2020-06-23T18:57:10.089400Z

That’s not what I’m getting

phronmophobic 2020-06-23T18:57:10.089600Z

basically, it can navigate to the "ratified" key

avi 2020-06-23T18:57:17.089800Z

oh wait…

phronmophobic 2020-06-23T18:57:22.090Z

I also removed the filterer

avi 2020-06-23T18:57:29.090200Z

ah interesting

phronmophobic 2020-06-23T18:57:33.090400Z

and the path exists if it can navigate there

avi 2020-06-23T18:57:45.090600Z

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

👍 1
phronmophobic 2020-06-23T18:58:04.090900Z

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

avi 2020-06-23T18:58:06.091100Z

cool, that’s clever, makes sense!

avi 2020-06-23T18:58:14.091300Z

that’s… surprising

avi 2020-06-23T18:58:17.091500Z

but, so be it

phronmophobic 2020-06-23T18:58:25.091700Z

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

phronmophobic 2020-06-23T18:58:56.091900Z

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

avi 2020-06-23T18:59:11.092100Z

some? ?

phronmophobic 2020-06-23T18:59:49.092300Z

(some? false) => true

phronmophobic 2020-06-23T19:00:15.092500Z

actually, boolean is probably clearer

avi 2020-06-23T19:00:36.092700Z

😵

phronmophobic 2020-06-23T19:00:39.092900Z

(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)

phronmophobic 2020-06-23T19:01:37.093100Z

some?'s doc string is correct:

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

avi 2020-06-23T19:02:28.093300Z

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

👍 1
avi 2020-06-23T19:03:08.093500Z

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!

👍 1