hi! i have a general "design" question regarding clojure specs and fdef. let's say i'd like to write a clojure lib, for which every public fn has it's fdef spec. should i rely entirely and only on specs to validate args ? or should i still add business logic to validate input args inside fns impl? should i expect my users to use the lib correctly according to the specs and write tests with stest/instrument? or not? i cant make up my own mind thanks a lot
• should i add a "conform" in every impl? and disable instrument in tests? isn itt redundant?
> should i rely entirely and only on specs to validate args ? If your contracts fit into specs - sure, why not. But some of the requirements might not easily be expressed with a spec. > should i expect my users to use the lib correctly according to the specs You should document how your lib should be used and expect that users use it according to the docs. The docs might refer users to the spec. > should i add a "conform" in every impl? Probably not.
hi @p-himik, thanks for your answer. >> should i add a "conform" in every impl? > Probably not. why ? for performance? when i read the docs of spec i cant get if i should use pre post conditions, assert or fdef. it's seems to me that the philosophy of it is to provide a consistent way of reporting validation errors. yet, if i use pre/post somewhere and conform elsewhere, and no validation at some other place, it feels broken.
do you know a well estalished clojure lib which make heavy use of specs on which i could get inspiration?
> why ? for performance?
For your own sanity. :) And simplicity.
If a function potentially can accept a sequence of any kind - just implement it so and no conforming is needed.
If a function must work with sets - just assume as much and document it, let the user decide and choose how that set is constructed.
If you would like to use s/or - just don't, in this case it's a recipe for a mess and harder maintenance.
There's that "be liberal in what you accept" maxima, but everything has boundaries. I'd say the boundary here is at the first item and the like, i.e. don't artificially constrain what your functions can accept. But you definitely shouldn't embed in your functions ways to convert anything to the desired input.
> i cant get if i should use pre post conditions, assert or fdef.
The first two are all assertions. They can be disabled and should probably be mostly about dev-time errors. Although I and plenty of others leave them enabled in production.
Pre/post conditions are ergonomic but aren't good for error reporting - you can't embed a message there. So I just don't use them at all.
fdef should only be used if you decide to go the spec route, and IIRC you have to instrument the code first for them to work, so anyone who doesn't instrument it won't see the errors at all.
> do you know a well estalished clojure lib which make heavy use of specs on which i could get inspiration?
Don't know right away, but I want to ask - why? Why do you actively want to use spec here? If it raises so many concerns, I'd probably stick to something simple and try spec bit by bit to see how it works out, instead of a full buy-in.
Clojure itself uses spec validation very conservatively, and I think only for compile-time things like defn and let. Not for run-time validation.
In general, I would say spec is a dev-time tool for aiding documentation and testing. It is not a tool for runtime error reporting or validation. This is partly because of performance, but also partly because the semantics of spec do not match what most people think of in terms of a data validation library, instead its semantics are very specific to the idea of a devtime contract validation library.
well i think of it as of racket contracts. which i was used to. i simply wanted to reproduce what i did in a racket lib i had
but maybe i misunderstood spec 's purpose
That's entirely possible. The library that gets used for runtime data validation in the clojure world is malli, not spec.
That said, I think it is generally against the grain for a library to be validating the input and output of its functions, most of the clojure ecosystem follows core with the garbage-in, garbage-out philosophy, and declares what allowed arguments are in documentation and expects users to follow through on that.
Everything get used. :) I'd just say that Malli fits the bill a bit better. But also not without limits of course. And yeah, I would not validate inputs to every function.
What I did with next.jdbc is to provide an optional ns that devs could require if they wanted fn checking, and it has explicit instrument/`unstrument` for opt-in and opt-out https://github.com/seancorfield/next-jdbc/blob/develop/src/next/jdbc/specs.clj
For more ways to think about Spec, I wrote about the ways we use it some years back: https://corfield.org/blog/2019/09/13/using-spec/
Kind of an idle curiosity on this topic - does anyone use core.contracts? I haven't used it, nor have I seen it used to enforce any kinds of constraints in any library that I've used.
https://clojure.github.io/core.contracts/
https://cloogle.phronemophobic.com/name-search.html?q=clojure.core.contracts%2Fwith-constraints&tables=var-usages also turns up almost no results either.
According to https://clojure.org/dev/contrib_libs that library is Inactive (no longer in development, will not be worked on again).
I mean, the same is true of spec isn't it?
@mgardner2 The same what is true of Spec?
> that library is Inactive (no longer in development, will not be worked on again)
Spec is essentially bundled with Clojure itself. It is stable and very widely-used.
exactly: other than bumping Clojure, the only changes in the last 3-4 years are fixing a typo and removing a duplicate combinator. That is not what I would consider active development
Because it is "stable" and "done". A lot of libraries in the Clojure world are stable and get almost no updates unless a bug is reported or changes are required for compatibility with a new version of the language.
right, but then why would that be a problem for core.contracts?
Now, Spec2 is "on-hold" as Rich got stuck in the hammock on a design consideration (about better integration of fn specs with defn, I believe).
core.contracts might well satisfy someone's needs as-is. But being designated "Inactive" means it's not likely to get updates even if bugs are found or updates to Clojure break it.
The page I linked to states the difference between Stable and Inactive.
I recently had clojure.java.jdbc moved from Stable to Inactive, for example, but it is widely used. I do not plan to do any work on it in future, however, because I would prefer to encourage folks to migrate to next.jdbc which is very actively maintained.
(it had been marked Stable for six years prior)
Sometimes libraries on that list change from Stable to Active (`clojure.java.data` is an example -- it had been Stable for ages, then I took it over because I wanted some bugs fixed and new features added -- to support next.jdbc in fact). A library could potentially change from Inactive back to Stable or even Active -- if a maintainer decides to come back to working on it, but I think that's pretty rare...
Interesting. Why identity?
$ clj -M -e "(macroexpand-1 '(.current java.lang.ProcessHandle))"
(. (clojure.core/identity java.lang.ProcessHandle) current)IIRC it's to have a target for metadata
identity is as good as any and primitive enough to be a target, do wouldn't do because it gets erased if it only has one argument
ok nice, thanks
https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/Compiler.java#L7622
yeah it's so that class literals can have a Class type hint