This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2018-02-21
Channels
- # architecture (9)
- # beginners (192)
- # boot (1)
- # bristol-clojurians (2)
- # cider (213)
- # cljs-dev (10)
- # clojure (195)
- # clojure-art (2)
- # clojure-austin (3)
- # clojure-belgium (4)
- # clojure-dev (4)
- # clojure-dusseldorf (1)
- # clojure-gamedev (9)
- # clojure-greece (21)
- # clojure-italy (27)
- # clojure-losangeles (2)
- # clojure-russia (1)
- # clojure-seattle-old (2)
- # clojure-serbia (1)
- # clojure-spec (114)
- # clojure-uk (136)
- # clojured (2)
- # clojurescript (100)
- # community-development (19)
- # core-async (12)
- # cursive (7)
- # duct (1)
- # figwheel (7)
- # fulcro (96)
- # hoplon (4)
- # jobs (2)
- # lein-figwheel (28)
- # leiningen (2)
- # luminus (14)
- # lumo (3)
- # off-topic (11)
- # om-next (2)
- # pedestal (10)
- # planck (11)
- # portkey (2)
- # proton (1)
- # protorepl (19)
- # re-frame (27)
- # reagent (12)
- # shadow-cljs (82)
- # spacemacs (42)
- # specter (15)
- # sql (3)
regarding multi-arity functions, can I spec a 4-arity fn this way?
(s/fdef select-events-sqlmap
:args (s/cat :domain :event/domain
:entity :event/entity
:key (s/alt :arity-2 nil? :arity-3-4 :event/key)
:version (s/alt :arity-2-3 nil? :arity-4 :event/version)))
It seems not to work when I pass three argsif there is a more idiomatic way I am open to it, this seems quite difficult to read
(s/fdef select-events-sqlmap
:args (s/cat :domain :event/domain
:entity :event/entity
:arity3-4 (s/? (s/cat :key :event/key :version (s/? :event/version)))))
Thanks a lot I will try this out!
tried this, but it seems to fail when :version
is nil
, will play more with it
you can wrap (s/nilable <spec>)
around a spec to also allow for nil
oh nice
this is what works:
(s/fdef select-events-sqlmap
:args (s/cat :domain :event/domain
:entity :event/entity
:arity3-4 (s/? (s/cat :key (s/nilable :event/key)
:version (s/? (s/nilable :event/version))))))
is one way
you can also s/alt at the top for the 2, 3, and 4 cases if you want a single map with the same keys conformed out
s/alt
only works with keywords so does that mean that I need to break the spec in three?
s/alt works with any spec - keywords are used to tag the alternatives
Can I make a spec definition that depends on another spec like:
(spec/def ::start-time
(spec/and string? valid-datetime?))
(spec/def ::end-time
(spec/and string? valid-datetime?)) ;; need to validate that it's greater than start-time
(spec/def an-entity
(spec/keys :req-un [::start-time ::end-time]))
@hawari.rahman17 you should do that validation in the ::an-entity
spec
Can you suggest on how can that be achieved? My apologies, I've just recently started using spec, I don't fully grasp its concept yet.
Do I use spec/and
and pair the spec/keys
with another predicate which validate that start-time is greater than end-time?
If so, how can one destructure a map from within a predicate? Does it get treated as an argument to a spec? @moxaj
Hi everyone. I'm a spec n00b so please have patience with my question here. Can/should I use spec for validating user input passed into my functions? For example, if I have a function that should accept positive integer values only and those value might need to be within a particular range, would spec work for this (and possibly what might the general structure of code look like)?
I wrote some examples that might help http://taylorwood.github.io/2017/10/15/fspec.html
Thanks Talyor 👍 I'll check that out now
also check out s/int-in for your range spec
Considering it makes a very very good parser for all sorts of data structures, that'd probably be a fine use case. Since the result is in itself more data, you could add more constraints in the future then build your error messages from that output.
At the moment, user input from a REPL.
via running lein repl and the calling the functions from the REPL
Ah, so you're talking runtime, not dev time, which is a big difference. At dev time you can just define your function specs and the user can decide to enable instrumentation (in my opinion). At runtime you'll be able to run your inputs through conform and respond with an informative error if it doesn't fit.
Can you explain what you mean by >the user can decide to enable instrumentation
Sure! So, if you define specs for all of your functions with s/fdef, nothing actually happens at runtime. It's just sort of there, like documentation.
The user, or developer, can decide to instrument a namespace / all namespaces so that these fdef specs are checked.
In normal spec, this only means the arguments being passed to functions are checked, but you can use something like Orchestra to check the return values too.
You may find https://github.com/bhb/expound interesting, it makes spec errors human readable at a glance. And here's https://github.com/jeaye/orchestra
Cool, thanks Oliver 🙂
No problem. I think you'll want to use https://clojuredocs.org/clojure.spec.alpha/conform on your data and a spec that checks your int is in range. For which you can use https://clojuredocs.org/clojure.spec.alpha/int-in-range_q
Perfection
user=> (s/conform #(s/int-in-range? 5 10 %) 2)
:clojure.spec.alpha/invalid
user=> (s/explain #(s/int-in-range? 5 10 %) 2)
val: 2 fails predicate: :clojure.spec.alpha/unknown
You can use (s/def ::my-num #(s/int-in-range? 5 10 %))
then (s/explain ::my-num 2)
gives you a more descriptive error too. Not sure how much spec you know though, so sorry if I'm explaining too much 😅
At present, I know about 2 hours worth 🙂
So all the explanation is very welcome
And if I'm using clojure 1.9.0, do I simply add this to my ns?
(ns my-ns.core
(:require [clojure.spec.alpha :as s]))
It should be present anyway, it's included as part of Clojure. No need to depend on it afaik
> Clojure 1.9 depends on this library and provides it to users of Clojure. Thus, the recommended way to use this library is to add a dependency on the latest version of Clojure 1.9, rather than including it directly
Yeah, it seemed to be throwing some error on my spec function without needing to depend so looks like 1.9.0 includes it by defaut
I think I've got a spec working without throwing an error when I run lein repl
. Excuse the trivial function example:
(defn my-fn
"An example function. Takes two arguments and makes a vector from them"
[a b]
(into [] (list a b)))
(s/fdef my-fn
:args (s/cat :a #(s/int-in-range? 1 11 %)
:b #(< 10 %)))
Does that seem correct?
And to get clojure to check this you have to use spec tools instrument or something like orchestra
So these are more of a developer tool thing, if you want to check things at runtime that you present to the user, you probably want s/conform and s/explain
So my next question then... if I run (my-fn 1 2)
within the REPL, it works instead of throwing an error
Is this where the spec tooling you're referring to would come into play?
Ah, that makes a lot of sense 🙂
Check out orchestra though, it has some really neat tooling https://github.com/jeaye/orchestra#defn-spec
@U38J3881W any ideas why I might be getting Could not locate orchestra/core__init.class or orchestra/core.clj on classpath
when running lein repl
?
I have [orchestra "0.2.0"]
in my dependencies and (:require [orchestra.core :refer [defn-spec]])
in my ns declaration
Ooh, odd. Maybe it's a doc thing? I suppose it's not found in [orchestra.spec.test :as st]
?
Also, maybe the docs are wrong in another way, try [orchestra "2017.11.12-1"]
, that one seems to be for newer versions of Clojure.
Hmm, not sure. They don't seem to include orchestra.spec.test in their defn-spec example on that readme
Isn't that version the cljs version?
I'm on 1.9.0
Let me try the other version and see
Thanks, that seems to have fixed it
Nice! I hope it's all working well. Tried it with expound yet? Definitely makes the errors more palatable.
Not yet
following on from hawari's example above, what's the best way to attach a generator to the compound spec that generates end-time by adding some random amount to start-time. assume the spec/keys call involves a lot of keys all the things that I'm coming up with have a lot of boilerplate, but I probably just don't understand test.check very well
what do you have so far?
cljs.user=> (s/def ::call' (s/keys :req-un [::direction ::duration ::agent_id ::group_id ::id ::start_time ::end_time]))
cljs.user=> (def call-gen #(gen/let [e (s/gen ::call')] (assoc e :end_time (+ (:start_time e) (rand-int 3600)))))
(s/def ::call (s/and (s/with-gen ::call' call-gen) #(> (:end_time %) (:start_time %))))
https://dev.clojure.org/jira/browse/CLJ-2197 I just ran into this :(((
hi, does spec have support for heterogeneous maps? in particular i want to validate a map like this: {:a-special-kwd [:some :elements] "string" ::some-structure "string2" ::some-structure ... }
hybrid map example
i suppose i could do: #(s/and (valid-special? %1) (s/valid? (s/map-of string? ::some-structure) (dissoc %1 :a-special-kwd)))
?
i suppose i was trying to avoid having lambdas which explicitly call valid?
though (if there is a way)
Hello! How do we check return values using fdef
and stest/instrument
? I see you can add the :ret
key, but it doesn't seem to fail when it should.
Also can stest/instrument
check all fdef
'd functions (instead of having to list them all)?
and calling (stest/instrument)
with no args should instrument everything that’s been loaded
Any way to check ret
without auto generating tests with check
?
We're unit testing and trying to use specs on the return value
I think someone has written some utility code for doing that, can’t remember which lib it’s in
We were looking for a way to continue using defn
, instead of defn-spec
, because defn-spec
confuses Cursive
i.e. just using orchestra’s instrument
seems like it will do :ret
checking on plain’ ol defn
s and s/fdef
s (hopefully?)
oh wow, that's exactly the answer I needed. We were fighting with Orchestra and Cursive for a while
FWIW (and it probably wouldn’t help you here) but Cursive has a feature where you can tell it to treat custom macros like the core/known macros, assuming the custom macro follows one of those known patterns
oh, we tried that. It partially worked, but then jumping between tests and implementation broke. Seems like the feature we want but it isn't quite fully baked yet
@bfabry ignoring the unrelated issues with using rand-int
, I think the boilerplate you have is necessary given the current API
maybe a helpful thing would be a variant of s/and
that lets you modify the default generator of the first argument
@johanatan I think you're looking for s/keys
@gfredericks what are the issues with using rant-int and what should I be doing instead?
using your own randomness instead of a generator means you lose growth, reproducibility and shrinking; one way to address that is to add a temporary key to ::call'
that maps to something that generates the sort of nonnegative integer you want, and then remove that key in ::call
oh yeah that's a good idea