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.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!
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.
@suskeyhose ^^^^
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.
Yeah, works great - if it didn't, I would be hurting
Hmm. Well now it's working on the small example. Curious why it's not working in my larger code.
likely something else is happening
Further, yielding sp/NONE for the value removes the element (for example, k/v pair in a map) which is really wonderful
Yeah, I've used that a few times, which is fantastic
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(transform [:key {:doesnt-exist :blah}] #(do (println %) :blah) {:key {}})
here's a minimal exampleso apparently I can't use a map as a key and have it work
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.AFAIK using maps as paths doesn’t do anything
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.
And just have to go back to traditional data manipulation
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
@suskeyhose ^^^
Okay, thanks. I'll take a look at it
@suskeyhose if you want to use a map as a key wrap it in keypath
oh, thanks!
keypath
is the navigator that's implicitly used by keywords in paths
Ah, okay. Well that's exactly the behavior that I'd wanted, so that's exactly it. Thanks so much!
out of curiosity, is it possible to provide custom implicit navigators?
@schmee yes, through the ImplicitNav
protocol
see https://github.com/nathanmarz/specter/blob/master/src/clj/com/rpl/specter.cljc#L1188