Fork me on GitHub
#specter
<
2018-09-23
>
Joshua Suskalo16:09:54

Hey, so I'm working with specter a bit, and one problem that I've noticed has come up several times which I feel has to have a normal solution, is that when I use transform there's no way to set the value if there is none already there. The solution I've had to use on several occasions is this:

(let [value (select-first [:path :to :val] structure)
      value (update-fn value)
      structure (setval [:path :to :val] value structure)]
  structure)
And this feels wrong on many levels, not the least of which because if structure is an atom, and I use ATOM in the path, that means that the operation is no longer atomic.

Joshua Suskalo16:09:19

Everything else I've used in specter always feels like a great addition to Clojure, but this one hiccup is something that I've run into enough times that it almost makes me want to express this instead using a normal swap!

jsa-aerial16:09:27

I'm a bit confused. This sort of thing (sp/transform [sp/ATOM key-path] update-fn db)) works just fine for me whether the final key exists or not. Also there is sp/BEGINNING and friends, which all work fine as well.

Joshua Suskalo16:09:28

Really? I'm not getting the function called at all with an example. Just a moment and I can give you the example that I'm running.

jsa-aerial16:09:47

Yeah, works great - if it didn't, I would be hurting

Joshua Suskalo16:09:01

Hmm. Well now it's working on the small example. Curious why it's not working in my larger code.

jsa-aerial16:09:21

likely something else is happening

jsa-aerial16:09:05

Further, yielding sp/NONE for the value removes the element (for example, k/v pair in a map) which is really wonderful

Joshua Suskalo17:09:15

Yeah, I've used that a few times, which is fantastic

Joshua Suskalo17:09:37

Okay, so the example in my code which this is working with is like this:

(transform [ATOM
            ::ds/rate-limits
            ::ds/endpoint-specific-rate-limits
            {::ds/action :create-message
             ::ds/major-variable {::ds/major-variable-type ::ds/channel-id
                                  ::ds/major-variable-value "286241942356885504"}}]
           #(println %)
           (atom #:discljord.specs{:rate-limits #:discljord.specs{:endpoint-specific-rate-limits {}}, :channel (a/chan 100), :token "TOKEN"}))
The println doesn't get called at all

Joshua Suskalo17:09:33

(transform [:key {:doesnt-exist :blah}] #(do (println %) :blah) {:key {}})
here's a minimal example

Joshua Suskalo17:09:41

so apparently I can't use a map as a key and have it work

Joshua Suskalo17:09:14

user> (select-first [{:blah :blah}] {{:blah :blah} :blah})
nil
user> (transform [{:blah :blah}] #(do (println %) :blah2) {{:blah :blah} :blah})
{{:blah :blah} :blah}
Here's some more stuff from my repl session which shows a more minimal case of failure.

schmee17:09:15

AFAIK using maps as paths doesn’t do anything

Joshua Suskalo17:09:56

Yeah, that's the problem. I have need of using a map as a key, and if specter can't select or transform paths with maps, then I'm kind of screwed.

Joshua Suskalo17:09:04

And just have to go back to traditional data manipulation

jsa-aerial17:09:55

Well, you could try sp/map-key which maybe will work - but you are still not screwed. But you will need to create a custom navigator : https://github.com/nathanmarz/specter/wiki/Cheat-Sheet#custom-navigators

Joshua Suskalo17:09:49

Okay, thanks. I'll take a look at it

nathanmarz17:09:55

@suskeyhose if you want to use a map as a key wrap it in keypath

nathanmarz18:09:30

keypath is the navigator that's implicitly used by keywords in paths

Joshua Suskalo18:09:05

Ah, okay. Well that's exactly the behavior that I'd wanted, so that's exactly it. Thanks so much!

schmee18:09:58

out of curiosity, is it possible to provide custom implicit navigators?

nathanmarz18:09:11

@schmee yes, through the ImplicitNav protocol

👍 4