Fork me on GitHub
#specter
<
2017-07-26
>
andrea.crotti09:07:27

I'm trying to implement something like this

(get-capitals {"A" ["hello"]
               "b" ["other"]})

;; should return {"A" ["hello"]}

andrea.crotti09:07:43

with Specter, which in theory it's just a select right?

andrea.crotti09:07:46

(defn get-capitals
  [probs]
  (specter/select
   [specter/MAP-KEYS #(Character/isUpperCase (first %))]
   probs))

andrea.crotti09:07:55

this loses the original structure though

schmee09:07:09

select always return a sequence, so if you want to maintain the structure you have to use something else

schmee09:07:12

gimmie a sec

andrea.crotti09:07:21

ah yes ok i can try with transofrm

andrea.crotti09:07:31

but the function itself should be identity then?

schmee09:07:47

this should do it:

(defn get-capitals
  [probs]
  (specter/setval
   [specter/MAP-KEYS #(Character/isLowerCase (first %))]
   specter/NONE
   probs)

schmee09:07:04

it removes everything that is lower case, instead of selecting what is upper case

andrea.crotti09:07:05

Ah this also works actualy

(defn get-capitals
  [probs]
  (specter/transform
   [specter/MAP-KEYS #(Character/isUpperCase (first %))]
   identity
   probs))

andrea.crotti09:07:21

I actually need both submaps really

andrea.crotti09:07:42

or maybe I can just use select to extract the two set of keys and reconstruct the maps later

frankmoyer21:07:02

I have a straight-forward selector that, does not function as I expect it to when the last element is index reference to a collection. Here is the select without the index in the keypath followed by the select with the index (0) in the keypath:

(sp/select [(apply sp/keypath [:db :right :text-entry 0 :offset])])
[({:text "180 West", :label ""})]

(sp/select [(apply sp/keypath [:db :right :text-entry 0 :offset 0])])
[nil]
The index navigator after :text-entry always works. It seems like it is just when the index is at the last position.

frankmoyer22:07:11

@nathanmarz I just figured out what was going on while I was compiling the input for you. A prior transform altered the portion of the structure that was being modified to be a List. When I convert it to a vec in the form, subsequent transform operations are able to navigate to the index. Thank you!

wei22:07:35

is there a way to make walker search only maps (not e.g. Datomic entitymaps?)

(def link-walker [(sp/walker ::link)])

nathanmarz22:07:57

@frankmoyer cool, figured it would be something like that

wei22:07:06

I’m confused why these give different results:

dev.user=> (sp/transform [(sp/walker ::link)] identity {:a 1 :b (d/entity (db/db) 17592186045447) ::link [:a 1]})
{:a 1, :b #:db{:id 17592186045447}, :dev.user/link [:a 1]}
dev.user=> (sp/transform [(sp/walker ::link)] identity {:a 1 :b (d/entity (db/db) 17592186045447)})
AbstractMethodError   clojure.lang.RT.conj (RT.java:667)

wei22:07:51

oh nevermind, I think I know why-- depth first search descends in the second case but cuts off in the first case

nathanmarz22:07:58

@wei walker will blindly walk into any collection (functions like clojure.walk)

wei22:07:28

thanks @nathanmarz. looking for a good alternative to walker then

nathanmarz22:07:28

(walker ::link) doesn't make much sense

nathanmarz22:07:43

look at the docstring

nathanmarz22:07:18

it's generally better to make your own recursive path tailored to the data structure / problem at hand

nathanmarz22:07:33

and it's really easy to do

wei22:07:00

I’m trying to find maps with the key ::link in them, so it seems like a legit afn to me? could you explain what I’m missing?

wei22:07:16

(in my example above, the map passed in was contrived and I substituted identity with a function that actually does something useful)

nathanmarz22:07:40

it's going to call ::link on all values / data structures it sees

nathanmarz22:07:01

e.g. (::link 1)

nathanmarz22:07:12

I wouldn't rely on that behavior

nathanmarz22:07:09

#(and (map? %) (contains? % ::link)) is better i think

wei22:07:41

ah that makes more sense. unfortunately it’s still descending into the datomic entity I think. is there a way to make walker short-circuit, or is there a better alternative?

dev.user=> (sp/transform [(sp/walker #(and (map? %) (contains? % ::link)))] identity {:a 1 :b (d/entity (db/db) 17592186045447)})
AbstractMethodError   clojure.lang.RT.conj (RT.java:667)

nathanmarz22:07:47

you can't avoid that with walker

nathanmarz22:07:57

an easy variant you could use is:

(def walk-skipper
  (recursive-path [afn skip-fn] p
    (cond-path (pred skip-fn) STOP
               (pred afn) STAY
               coll? [ALL p]
               )))

nathanmarz22:07:09

set the skip-fn to return true on datomic entities

nathanmarz22:07:15

or anything else you don't want to walk

wei22:07:25

trying to understand recursive-path usage, where does pred come from in this case?

nathanmarz22:07:48

pred is a navigator

nathanmarz22:07:03

that's what's implicitly used when you put a function into a path

nathanmarz22:07:22

it's used in this case to avoid runtime conversion of function -> navigator

nathanmarz22:07:53

(for performance)

wei22:07:06

great, seems to be working as expected! i substituted com.rpl.specter/pred so I could define walk-skipper in my own ns. thanks for your help.

wei23:07:14

sorry to bug you again @nathanmarz but the walk-skipper doesn’t seem to be working for me:

(def walk-skipper
  (sp/recursive-path [afn skip-fn] p
                     (sp/cond-path (sp/pred skip-fn) sp/STOP
                                   (sp/pred afn) sp/STAY
                                   coll? [sp/ALL p])))

dev.user=> (sp/select [(sp/walker #(and (map? %) (contains? % :d)) )] {:a 1 :b 2 :c {:d 1}})
[{:d 1}]
dev.user=> (sp/select [(walk-skipper #(and (map? %) (contains? % :d)) (complement map?))] {:a 1 :b 2 :c {:d 1}})
[]

wei23:07:07

still wrapping my head around how recursive-path works. mind helping me troubleshoot?

nathanmarz23:07:30

@wei the definition of walker / walk-skipper traverses all collections via ALL to replicate clojure.walk semantics

nathanmarz23:07:52

so that means it traverses each key/value pair as a vector, then traverses those to each key/value

nathanmarz23:07:17

since you stop navigation if encountering a non-map, it stops traversing once it reaches key/value pairs

nathanmarz23:07:23

so never reaches the inner map

nathanmarz23:07:54

like I said, it's better to make your own recursive path tailored to the data structure / problem at hand

nathanmarz23:07:28

in this case you probably want to descend using MAP-VALS rather than ALL