practicalli / practicalli/clojure
Clojure spec in practice
Nobody has claimed this yet.
- Dominant language
- Makefile
- Stars
- 117
- Forks
- 36
- PR merge metrics
- No merged PRs in 30d
Description
So maybe this is a naïve question, but…
Say I’ve registered a spec for my fn:
(s/fdef import-post!
:args (s/cat :remote-post post-spec/remote-post)
:ret post-spec/intern-post)
Is there any way to use the definition here of the spec for :args within import-post! to validate my input?
Or can this spec only be used to validate the whole contract, with both input and output?
you can do this within :pre - there are examples of this in the clojure spec guide
instrument only checks :args
https://github.com/jeaye/orchestra
clojure.spec only instruments :args, ommitting :ret and :fn from automatic validation Orchestra checks all
Which seems to work - :ret parts are validated in my tests now.I'm wondering why clojure.spec doesn't have this. There must some good reason which I'm not aware of yet
Is using orchestra considered more like an antipattern?
I am not aware of that
noisesmith 03:06
be aware that if you instrument your function, and one of your args is specced as a function, random data will be generated and passed to that functionso don't do this with side effecting function args (edited)
seancorfield 03:06
Also "validate" is a bit imprecise: to me, that means a production run-time validation of data and appropriate error handling on failure 🙂
Bill Phillips 03:07
The spec guide shows :pre, but: (1) doesn’t illustrate referring to :args within the fdef, and (2) I asked about that and apparently it’s not a recommended practice?
seancorfield 03:08
https://corfield.org/blog/2019/09/13/using-spec/ talks about various ways to use Spec in dev/test/production code.
corfield.org
How Do You Use clojure.spec - An Architect's View
An interesting Clojure question came up on Quora recently and I decided that my answer to “how do you use clojure.spec” there should …
Bill Phillips 03:08
Yeah, that’s what I’m referring to, Sean. I’ve got a function that currently falls down on bad input, and I’d like a clear failure
noisesmith 03:08
IMHO that's what :pre is for
03:09
don't instrument in prod
seancorfield 03:09
:pre and instrument are for dev/test really, not production -- for a lot of people -- because they're about assertions
noisesmith 03:09
fdef is for instrumenting as I understand it
didibus 03:09
I'd say for things you want to validate in prod, just use s/valid? (edited)
Bill Phillips 03:09
Yeah, instrument seemed like the wrong tool
seancorfield 03:09
Yup. If you want this in production use s/valid? or s/conform and have an explicit handler for invalid data.
Bill Phillips 03:12
I’ve written this:
(defn import-post!
[remote-post]
(if (not (s/valid? (s/cat :remote-post post-spec/remote-post) [remote-post]))
(s/explain (s/cat :remote-post post-spec/remote-post) [remote-post])
...
…setting aside the obvious repetition, the spec (s/cat :remote-post post-spec/remote-post) is precisely what I’ve written in :args in my fspec (edited)
didibus 03:13
In theory, it would go: You've run st/check on all your functions, you've instrumented your code in devo while doing manual testing and when running your unit and integ tests. So you know everything works. But, there are some things that depend on runtime input, like input from a user, an API, a file, the DB, etc. Since there's no way to know whatever gives you this input won't give you garbage, for that, you add an explicit s/valid? or s/conform.
alexmiller 03:14
or s/assert
didibus 03:14
That looks good to me, though you'd probably want to s/def that spec so you're not repeating it like you said
03:15
I don't like s/assert as much, because the rules around how to enable/disable it confuse me too much 😛
03:15
Also generally I like to control in prod the error returned or thrown with more granularity, but yea, it is also an option
Bill Phillips 03:20
How would I use s/def here? I thought it was used to wire namespaced keys to data specs?
didibus 03:22
Oh sorry
03:22
You already have a spec for it, so no, that's wrong
03:23
You want:
(defn import-post!
[remote-post]
(if (not (s/valid? :post-spec/remote-post remote-post))
(s/explain :post-spec/remote-post remote-post)
...
(edited)
03:23
sorry, edited it
Bill Phillips 03:35
is there any particular reason this isn’t a crazy idea:
(defn valid-against-spec? [spec args]
(if-not (s/valid? spec args)
(s/explain spec args)
true))(let [args-spec (s/cat :remote-post post-spec/remote-post)]
(defn import-post!
[remote-post]
(when (valid-against-spec? args-spec [remote-post])
...)) (s/fdef import-post!
:args args-spec
:ret post-spec/intern-post))
03:36
apart from being kind of ugly and poorly named
didibus 03:49
Hum..., I think s/fdef is a macro no? Can it capture the value of the let like that I'm not sure
03:52
What's your hesitation for doing:
(defn valid-against-spec? [spec args]
(if-not (s/valid? spec args)
(s/explain spec args)
true))(defn import-post!
[remote-post]
(when (valid-against-spec? :post-spec/intern-post remote-post)
...)) (s/fdef import-post!
:args (s/cat :remote-post :post-spec/intern-post)
:ret post-spec/intern-post))
03:54
It's like less code, and visually less ugly
noisesmith 03:58
that when is always true (edited)
03:59
oh it's a println error log, never mind
didibus 04:11
If you really wanted to reuse the args spec of fdef, there's a way to get it from the fdef I think. Can't remember on top of my head though
04:13
But still, to me those are different. You're trying to validate remote-post, and you already have a spec for it. The s/cat is not the spec of remote-post, it's the spec of the argument vector of the function, that's why you need to do the shenenigan of converting remote-postinto a vector before you validate it against the arg vector spec. So I just feel it's a roundabout way.
alexmiller 04:17
If you s/get-spec the var symbol, that’s an fspec that supports keyword lookup of :args, :ret, :fn (edited)
didibus 04:31
Ah ya, that's how
Bill Phillips 04:53
(s/explain (s/cat :remote-post :post-spec/intern-post) [remote-post]) doesn’t yield as nice an error message as (s/explain :post-spec/intern-post remote-post).
04:53
b/c it’s not really validating the same thing: the former is validating the arg list, the latter is validating an individual argument by hand
04:54
i’ll try that, alexmiller. i didn’t think to just (:args (s/get-spec 'symbol))
04:59
the docs for what fdef registers and get-spec yields are opaque
nick 07:18
replied to a thread:
@Bill Phillips instrument only checks :args -- you're aware of that?
I've recently found a library called https://github.com/jeaye/orchestra
By default, clojure.spec will only instrument :args. This leaves out :ret and :fn from automatic validation; Orchestra checks all of them for you.
Which seems to work - :ret parts are validated in my tests now.I'm wondering why clojure.spec doesn't have this. There must some good reason which I'm not aware of yet 🙂
Is using orchestra considered more like an antipattern?
jeaye/orchestra
Complete instrumentation for clojure.spec
Stars
494
Language
Clojure
https://github.com/jeaye/orchestra|jeaye/orchestrajeaye/orchestra | 29 Mar 2017 | Added by GitHub
andy.fingerhut 07:19
There question is probably in an FAQ about spec somewhere -- it is definitely frequently asked.
One of the reasons orchestra exists is because many people want return value spec checking on many functions during testing, despite spec not helping you do that, except when running tests specifically on that function
nick 7 hours ago
Not sure I understand.From what I see I can just use orchestra.spec.test/instrument and it checks all specs, no need to call instrument on every function.
andy.fingerhut 7 hours ago
If you do not use orchestra, only the instrument that comes with Clojure.spec, no ret value checking is performed. orchestra does do ret value checking
nick 7 hours ago
oh Got it
andy.fingerhut 7 hours ago
You asked why clojure.spec doesn't have this -- this is asked so often, I am sure someone has written an answer to "why?' somewhere, but not sure where at the moment
nick 7 hours ago
Found it on https://blog.taylorwood.io/2018/10/15/clojure-spec-faq.html"Q: Why doesn’t instrument check my function return value?"
blog.taylorwood.io
Clojure.spec Beginner's FAQ
Clojure.spec has been available (in alpha) for some time, and there are great talks and resources like the rationale and official guide. This post supplement...
andy.fingerhut 7 hours ago
That is a guessed reason by the author of that article, who did not write spec, nor apparently did they ask the author of spec
andy.fingerhut 7 hours ago
Here is an older answer to the question, plus a link to where Rich Hickey answered it: https://groups.google.com/forum/#!msg/clojure/JU6EmjtbRiQ/uND70kAFBgAJ
nick 7 hours ago
Thank you very much Andy!
andy.fingerhut 7 hours ago
Ah, and there is also a brief answer in clojure.org's faq list here: https://clojure.org/guides/faq#instrument_ret
andy.fingerhut 7 hours ago
As I said, enough people find that answer unsatisfying that orchestra helps you do what many people would like to
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the issue discussion and the linked clojure.org FAQ, Taylor Wood FAQ, and Clojure mailing-list answer. The payload names no repository file or test and does not define a documentation change; first establish whether this question should become a documented FAQ entry. Done would require an agreed scope and a specific documentation location.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- clojure
- Domain
- documentation
- Issue type
- Documentation
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Needs clarification
- Newbie friendliness
- 15/100