Fork me on GitHub

just like defn-spec adds inlined specs for function return values and parameters, defrecord-spec adds inlined specs for fields, along with adding specs to the built-in record construction functions


eh, s/inlined/inplace

Alex Miller (Clojure team)00:02:01

Ah. Rich is considering a spec-enhanced defn.


defn and defrecord are the obvious ones to gain spec-enhanced forms. specs for protocol implementations could also be nice (in defrecord/deftype or extend-type). default specs for protocols (in defprotocol)? protocol related stuff I haven’t thought enough about. but have noticed it feels like a “spec gap”

Alex Miller (Clojure team)00:02:56

Protocols can’t be spec’ed


@mrevelle if you keep your protocol an implementation detail and dont expose it as your public api it’s not a problem


for example (my-func) could be a spec’d function and (-my-func) could be a protocol function


then (my-func) calls (-my-func) internally


This is what we did with hoplon

Alex Miller (Clojure team)01:02:55

These days we consider that a best practice in the core team


protocols could be spec’d


@flyboarder yeah, I suppose. not a fan of the redundancy and lack of documented expectations that specs may provide. it can also be useful to allow others to implement your protocols (see loom and ubergraph)


What am I doing wrong here?


user> (require '[clojure.spec.alpha :as s])
user> (require '[clojure.spec.test.alpha :as stest])
user> (require '[clojure.spec.gen.alpha :as sgen])
user> (s/def ::one-arg-fn (s/fspec :args (s/cat :x any?)))
user> (defn map-vals
        "Map the function f over all the values of the associative collection coll."
        [f coll]
        (reduce-kv (fn [m k v]
                     (assoc m k (f v)))
                   (empty coll)
user> (s/fdef map-vals :args (s/cat :f ::one-arg-fn :coll associative?))
user> (def xmap {:a " foo" :b "bar "})
user> (stest/instrument)
user> (map-vals clojure.string/trim xmap)
Execution error - invalid arguments to user/map-vals at (REPL:65).                            
(nil) - failed: (apply fn) at: [:f] spec: :user/one-arg-fn                                    

Alex Miller (Clojure team)04:02:06

when you use an instrumented fspec, it will actually generate values according to the fspec args spec and invoke the function you pass with them

Alex Miller (Clojure team)04:02:19

here you declared that the fspec function takes an any? arg

Alex Miller (Clojure team)04:02:32

the generator generated nil and invoked clojure.string/trim with it


Ah. Thanks. So, I should use a s/with-gen in the spec definition of ::one-arg-fn?

Alex Miller (Clojure team)04:02:05

so it's effectively telling you that when passing clojure.string/trim to map-vals, you have passed a function that will not take an any?

Alex Miller (Clojure team)04:02:38

or instead of fspec, many people find it easier to just use ifn?

Alex Miller (Clojure team)04:02:00

this generative test on instrument of fspec is a feature that many people find surprising (or wrong)

Alex Miller (Clojure team)04:02:15

and something we're going to revisit in spec 2


OK. Thanks for the explanation. What would be the recommended way in spec1 to write a spec for a single arity function?


Or, is the recommendation to just use ifn? as you mention above?

Alex Miller (Clojure team)04:02:48

with current state, I don't find that I gain anything but headaches with fspecs


What would be the best way to prepare for spec 2. I now sometimes use the :opt is it useful to rewrite those to :req and make the values also allow nil?


@gklijs s/keys will stay as-is, as far as I know, so you will be able to move to s/schema and s/select piecemeal over time as you need it.


I have our codebase running against spec2 with minimal changes (by depending on the latest git SHA of clojure/spec-alpha2 -- since we use CLI/`deps.edn`) but it's still very much a moving target with no formal release (and still some bugs being worked out).


The main thing you may trip over is if you have used a predicate in a context that is really expecting a spec -- spec1 allows that but spec2 does not. Also, some constructs are now stricter about what constitutes a "spec".

Alex Miller (Clojure team)06:02:10

the fate of s/keys is not yet determined


Oooh... so it may go away before spec2 is "released"?

Alex Miller (Clojure team)06:02:22

maybe, but really don't know yet


That would make the migration from spec1 to spec2 a lot more work!

Alex Miller (Clojure team)06:02:37

at the moment I'd say it's more likely to still be there

Alex Miller (Clojure team)06:02:28

the idea behind schema has shifted a lot since Rich's talk, not sure if it will survive in the form described then

dafuq 10
Alex Miller (Clojure team)06:02:44

he's deep in the hammock :)

🌴 15
clj 5

Interesting... I look forward to seeing how this evolves then...


And, yeah, I feel that hammock... I'm still deep in thought about next.jdbc 🙂

Alex Miller (Clojure team)06:02:20

@gklijs I would not rewrite opts to nilable reqs - I think that's the wrong direction

Alex Miller (Clojure team)06:02:44

really, unless you're a "riding the bleeding edge" guy like Sean, I would just wait

Alex Miller (Clojure team)06:02:54

select will have both required and optional selections

Alex Miller (Clojure team)06:02:29

although the semantics around optional stuff is a little different


Ok, I'll leave it for now then. I now use it as part of (de)serialisation, with optional added defaults on a subset of what can be specced, also using the spec's to generate the cljs functions to edit the data. I probably need to write some sort of migration at a point, but that should be trivial. Might use another namespace to spec the existing data in spec2 at some point.


If you have two specs but you want to have a shared id between the two, is that possible in a generator? But also the id’s are generated as well. 👀


example please?


you'll need custom generator, which probably will have to generate examples for both specs at the same time. Another funky option is to override default id gen to generate predefined small set of ids, and then filter out examples generated by "default" gens which happen to share an id

👍 5

thanks! that makes sense


first option would look like: 1) gen set of ids, 2) gen set of obj1 with default gen 3) gen set of obj2 with default gen 4) override ids in pairs of obj1 obj2 with ids from 1)

👌 5

this will allow you to leverage default spec gens w/o any modification, and just build higher level one


writing a gen for entire family of interdependent objs is hard -_-