This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2018-02-27
Channels
- # aleph (7)
- # beginners (80)
- # boot (1)
- # cider (3)
- # cljs-dev (277)
- # cljsjs (52)
- # cljsrn (1)
- # clojure (69)
- # clojure-gamedev (4)
- # clojure-italy (1)
- # clojure-losangeles (2)
- # clojure-russia (89)
- # clojure-spec (92)
- # clojure-uk (196)
- # clojured (1)
- # clojurescript (70)
- # cursive (5)
- # data-science (1)
- # datascript (84)
- # datomic (9)
- # defnpodcast (12)
- # docker (1)
- # emacs (4)
- # events (1)
- # fulcro (112)
- # graphql (1)
- # jobs (1)
- # lumo (1)
- # nrepl (21)
- # off-topic (2)
- # onyx (3)
- # protorepl (10)
- # re-frame (23)
- # reagent (66)
- # reitit (2)
- # rum (13)
- # shadow-cljs (144)
- # spacemacs (14)
- # sql (4)
- # unrepl (29)
- # vim (16)
I have written a macro to get rid of some boilerplate. I’m using a spec to validate arguments passed to a generated function.
(s/merge ::util/job-args
(s/keys :req-un
[::importers
::database
::rabbitmq
::stats]))
But I still have to destructure the args
, where something could go wrong if you make a typo.
(let [{:keys [report importers database rabbitmq stats]}
args]
)
Is there a good solution for this, e.g. key destructuring using spec?I'd probably use plumbing/safe-get
yeah, I thought about writing that before I wrote the specs… might be just as effective
theoretically it overlaps with spec because the spec could tell you whether you're accessing a key that can be ex-spec-ted to be there
@borkdude I’ve written this https://github.com/stathissideris/spectacles but it doesn’t cover destructuring
it has get, get-in, assoc, assoc-in, update, update-in
you can also compose the lenses
@gfredericks should it, say, throw, if you are asking for key not in spec?
@misha that sort of thing, yeah
that’s what spectacles does! (more or less)
if someone wants to extend it with let-speced
pull requests are very welcome
I think you are missing binding part of destructuring, @stathissideris
definitely, but there is quite a bit of code there to introspect the specs etc
so it’s a good base to build on, that’s what I’m saying 🙂
I think the most common and useful case would be specced-destructuring for maps (s/keys)
the main reason I didn’t do it was the syntax, couldn’t come up with a nice way to express it
because the spec name has to be mentioned
it’s already a bit awkward with get-in
@misha can you come with an example of what it would look like using it?
not yet, thinking of options.
what I know, is:
- binding has to be a core feature, to avoid repetition
- should look "familiar", so plugins, like #cursive could understand it (most probably like let
)
- should be natural to use, because following does not really looks good:
(let [_ (spec-bind spec m [foo bar baz])]
...)
how about:
(lens/let [{::logging [::level ::destination ::type]} foo]
(prn "level is" level)
(prn "type is" type))
instead of :keys
you give the name of the spec
(haven’t worked out the implications of the syntax yet)
yeah, the ideal situation would be for spectacles to handle the “special” parts and pass on anything not related to spec to clojure.core/let
up next: how to bind keys from the same map using different specs? 2 lines? or 2 specs in destructuring form?
(lens/let [{::storage [::url ::port ::password]} foo
{::logging [::level ::destination ::type]} foo]
(prn "level is" level)
(prn "type is" type))
hmm, it will throw if you try to destructure a key that is not speced, but not when the map contains extra keys
a week ago we were discussing adding &
to the map destructuring, so I spent some time looking at clojure.core/destructure
actually my problem was like this:
my-ns=> (s/def ::job-args (s/keys :req-un [::report ::scheduler]))
:my-ns/job-args
my-ns=> (lens/get {:scheduler 1} ::job-args :scheduler)
1
my-ns=> (lens/get {:foo 1} ::job-args :scheduler) ;;=> nil
So the input map should conform to the spec, which I can validate with e.g. s/assert
, s/valid?
etc.@borkdude are you saying that the last call should throw instead of returning nil?
I think the value proposition here would be, "(say) throw, if trying to bind value under key not in spec"
if we are still on let-like
macro topic: I'd just check if the key is supposed to be in map according to spec,
because validation can be expensive, and I'd like to be able to separate the two
safe-get
is the most effective in my case here: I just get an exception and prints out the keys that were in the map and the key I used. I can figure out which one was wrong
I guess you can mess up specs themselves too, nothing protects you from typos… typos all the way down
from a broader perspective, destructuring and select-keys - are a bit orthogonal to each other, and both would need to be addressed individually
⌨️ :thunder_cloud_and_rain:
@misha select-keys would be a good (and easy) addition to spectacles
I just discussed select-keys with someone else. After you use select keys you can still mess up selecting from the result of select keys
not if you’re using lens/get
! 🙂
would be good to work out a good syntax for this
if collision with :my-ns/keys can be solved - it is the best syntax there could be, already
Personally I would just add a :spec key in the map destructuring form and limit it at that: {:as foo :keys [..] : spec ::foo}
just don’t ever call your specs ::keys
😂
another data point, i usually don't use keys in :keys, but symbols
(let [{:my.ns/keys [foo bar]} m])
@mpenet that does sound a bit less collision prone, thank you!
@mpenet you then miss on opportunity to destructure by multiple specs in the same map :)
@mpenet I’ve added your idea to the issue
with attribution!
spectacles relies on looking at forms to figure out the keys
but yeah it feels risky
Yup, spec-tools, speculate, etc been there done that too and trying to avoid doing it from now on;)
spec-based seq destructuring could check that (1) the spec is in fact a coll-of, a cat or a tuple (2) for finite length specs, you’re not attempting to destructure more elements than what is defined in the spec
not sure how useful…
sorry I’m not sure what that emoticon means!
@misha thanks for the comment
yeah maybe not really worth it
@stathissideris you need to recursively call s/form until you get actual form back, here (and pretty much everywhere where you call s/form)
https://github.com/stathissideris/spectacles/blob/master/src/spectacles/impl.clj#L38