Fork me on GitHub

Hello, I may have found a typo in the spec guide. > The second :ret predicate takes as input the conformed result of the first predicate and verifies that start < end. The :ret value > spec is also an integer. Finally, the :fn spec checks that the return value is >= start and < end. I believe “The second :ret predicate” should be “The second :args predicate”.


I like putting my s/fdef before the fn definition, but when I have a :fn argument which needs a reference to the fn itself (e.g. to specify that the fn is idempotent, or maybe inverts on a given arg), it’s a forward reference and thus I must declare the symbol first. Would it be reasonable for the :fn arg to be called with a reference to the function under spec in its map arg?


Hurrah! glad to help

Alex Miller (Clojure team)14:08:25

@donaldball: what do you mean by “reference to the function” ? do you mean a key that refers to the function? what would the value be?


In this case, simply avoiding the necessity of declaring a forward reference to the function being specified, since s/fdef precedes defn

Alex Miller (Clojure team)14:08:12

can you give me an example of what that would look like? I don’t think this is something we would do.


(declare convert)

(s/fdef convert
  :args ...
  :ret ...
  :fn (fn [{:keys [args ret]} map]
        (= ret (convert ...))))

(defn convert

Alex Miller (Clojure team)14:08:05

but what would your replacement look like?


You could add the function-under-spec to the map given to the :fn callback fn

Alex Miller (Clojure team)14:08:48

it seems like a smell that you would be calling the function in the :fn spec


The other possible use case I was ruminating about the other day would be higher-order fns to specify that e.g. the specified fn is associative, commutative, has a given identity value


Maybe these cases shouldn’t be specified here, but a priori it doesn’t seem unreasonable


To me, at least, that starts to seem overspecified, and kind of feels like “the function does what it does”. (I know you mentioned some cases where it would make more sense than that.) I’ve been wrestling with the tension of how exhaustively to specify functions … there’s a point where it starts to feel a bit ridiculous. There’s still a place for prose documentation, and I think there’s also still a place for traditional test.check property tests to deal with some properties.


yeah those things definitely smell a lot more like normal test.check tests


That tension has always existed in contract systems. How do you specify “what the function is supposed to do” without simply restating the (possibly incorrect) code in the function? For some things that’s fairly easy, but for other things it becomes very difficult. In such cases, the typical practice is to include some simple sanity checks, and use traditional testing methods to validate correctness. It seems to me like clojure.spec raises the bar for what makes sense to put in the spec (or contract) … but the bar is still there.


So far to summarize that best practices for the :fn arg of s/fdef is strictly to specify some aspects of the relationship between specific args and return values, not properties of the function more broadly?


The rule of thumb I’ve got so far is that I stop trying to make the fdef more thorough when the validation goal starts to overwhelm the usefulness of the spec for internal documentation purposes. (In other words, I don’t want the spec to be so complex that a programmer trying to work in the file has to stop and really think through complex details in the fdef to figure out how the thing is supposed to work.)


I don’t know if that’s the best practice or not, but for something that claims “you get a lot of different uses out of the same thing” that kind of balance strikes me as important.

Alex Miller (Clojure team)15:08:29

complicated :fn should (sometimes) also raise questions about whether your function is doing too much