clojure

parasitid 2025-09-04T08:31:53.018199Z

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

parasitid 2025-09-04T08:36:47.075099Z

• should i add a "conform" in every impl? and disable instrument in tests? isn itt redundant?

p-himik 2025-09-04T08:39:25.108809Z

> 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.

parasitid 2025-09-04T08:56:20.697169Z

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.

parasitid 2025-09-04T08:57:38.918929Z

do you know a well estalished clojure lib which make heavy use of specs on which i could get inspiration?

p-himik 2025-09-04T12:58:09.144919Z

> 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.

p-himik 2025-09-04T13:02:46.097199Z

Clojure itself uses spec validation very conservatively, and I think only for compile-time things like defn and let. Not for run-time validation.

2025-09-04T13:07:06.611259Z

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.

parasitid 2025-09-04T13:08:35.890429Z

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

parasitid 2025-09-04T13:09:00.844819Z

but maybe i misunderstood spec 's purpose

2025-09-04T13:09:28.977279Z

That's entirely possible. The library that gets used for runtime data validation in the clojure world is malli, not spec.

2025-09-04T13:10:26.832119Z

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.

p-himik 2025-09-04T13:10:45.154759Z

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.

👍 1
seancorfield 2025-09-04T13:10:45.356039Z

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

seancorfield 2025-09-04T13:12:06.232379Z

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/

respatialized 2025-09-04T16:38:40.695309Z

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.

seancorfield 2025-09-04T17:48:59.396929Z

According to https://clojure.org/dev/contrib_libs that library is Inactive (no longer in development, will not be worked on again).

✔️ 1
MJ 2025-09-04T18:42:24.668279Z

I mean, the same is true of spec isn't it?

seancorfield 2025-09-04T19:18:28.046499Z

@mgardner2 The same what is true of Spec?

MJ 2025-09-04T20:22:04.686919Z

> that library is Inactive (no longer in development, will not be worked on again)

seancorfield 2025-09-04T20:23:37.478409Z

Spec is essentially bundled with Clojure itself. It is stable and very widely-used.

respatialized 2025-09-04T20:24:31.084469Z

https://github.com/clojure/spec.alpha/commits/master/

MJ 2025-09-04T20:29:36.670849Z

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

seancorfield 2025-09-04T20:30:43.355129Z

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.

MJ 2025-09-04T20:31:29.739549Z

right, but then why would that be a problem for core.contracts?

seancorfield 2025-09-04T20:31:44.856289Z

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).

seancorfield 2025-09-04T20:33:15.746169Z

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.

seancorfield 2025-09-04T20:35:19.957459Z

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.

seancorfield 2025-09-04T20:37:41.000559Z

(it had been marked Stable for six years prior)

seancorfield 2025-09-04T20:41:02.657929Z

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...

borkdude 2025-09-04T12:51:50.486339Z

Interesting. Why identity?

$ clj -M -e "(macroexpand-1 '(.current java.lang.ProcessHandle))"
(. (clojure.core/identity java.lang.ProcessHandle) current)

bronsa 2025-09-04T12:55:45.295009Z

IIRC it's to have a target for metadata

bronsa 2025-09-04T12:56:28.813059Z

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

borkdude 2025-09-04T12:56:49.566869Z

ok nice, thanks

bronsa 2025-09-04T12:59:40.561319Z

yeah it's so that class literals can have a Class type hint

👍 1