clojure-spec 2021-09-13

Question regarding conventions and style. For some map spec ::foo having a few variants, how would you name the specs? Should keys be named :foo/key? For the variants I thought of just using or (s/def ::foo (s/or ,,,)) To name the variants I can think of ::foo.a ::foo-a :foo.type/a Recommendations and ideas very welcome

Alex Miller (Clojure team) 2021-09-13T11:34:53.080300Z

There’s not enough detail here for me to really answer that - how does a variant vary?

Alex Miller (Clojure team) 2021-09-13T11:37:20.083200Z

Generally though, I would urge you first to focus on the attributes not on the maps. Name the attributes well and fully describe the set of values that attribute can cover. If you then need multiple sets of attributes, kind of depends how they relate

Right, then to be specific, consider https://clojure.github.io/tools.analyzer.jvm/spec/quickref.html#binding, which I want to spec out unambiguously

if you don't mind my asking, are you building something off t.ana? Would be curious about it (as someone who maintains two derived tools namely Eastwood and refactor-nrepl)

I am exploring the possibilities. Background, clj-fast, one of my first projects, is just a bunch of heuristic inlinings and optimizations of core clojure. I can keep on working at it for every conceivable use case, but I became interested in solving the general problem. I want to implement some intermediate compiler passes - constant inlining, beta reductions, copy and constant propagation. If I get this, I can write a compiler from clojure to clojure that cuts down on the biggest performance hits in the language, iteration and dynamism

πŸ’― 1

This ia clearly not for for development time, but for final artifacts it could be huge

Alex Miller (Clojure team) 2021-09-13T12:03:13.084500Z

this is probably a good use case for s/multi-spec

Alex Miller (Clojure team) 2021-09-13T12:03:40.084700Z

switching on :op

But even before op, zooming in on binding

A very rough spec would be:

(s/def ::binding
  (s/keys
   :req-un
   [:binding/op :binding/form :binding/name :binding/local :binding/arg-id
    :binding/variadic? :binding/init :binding/atom :binding/children]))

(imagine I merged it with ::common)

I should have split out the :opt-un, too

but the point is, there are keys which just don't occur some times

for example, :binding/arg-id occurs only when :binding/local is :arg

Alex Miller (Clojure team) 2021-09-13T12:05:38.087900Z

you could do a second level of s/multi-spec on :local

So I need to "subtype" bindings then or between them

is there a reason to prefer multi-spec to or?

Alex Miller (Clojure team) 2021-09-13T12:06:24.088800Z

it's designed for open extension, and it doesn't tag the conformed value

okay, and how would you name the specs you select between? would you even name them?

Alex Miller (Clojure team) 2021-09-13T12:08:38.090300Z

I think I would name them, and I would name them something related to the ast node type

Alex Miller (Clojure team) 2021-09-13T12:09:42.091500Z

in general, I find relying on s/merge and s/multi-spec to "build up" evolves better than trying to s/or

I have no sense of style or habits with spec so I thought it would be better to ask instead of produce something horrendous πŸ™‚

how would you name the specs you dispatch to? ::arg-binding , ::binding.arg , :binding.type/arg?

something else entirely?

Alex Miller (Clojure team) 2021-09-13T12:29:44.094500Z

.'s should generally be used only in qualifiers, so I'd rule out the middle one

Alex Miller (Clojure team) 2021-09-13T12:30:29.095200Z

since these are ast nodes I would probably be trying to include either "ast" and/or "node" in the name of each one

Alex Miller (Clojure team) 2021-09-13T12:31:39.096500Z

whether you put that in the qualifier or in the name is a matter of taste

In that case, I can even start from naming it something like :node/binding then the multi case could be :node/binding.arg? :node/arg.binding?

Like I said previously, I still have no sense of taste when it comes to specs, so I prefer to ask someone with experience

Alex Miller (Clojure team) 2021-09-13T12:32:18.097800Z

"node" is not sufficiently unique - you should treat this as a global naming space

Alex Miller (Clojure team) 2021-09-13T12:32:39.098100Z

:clojure.tools.analyzer.node/binding

Alex Miller (Clojure team) 2021-09-13T12:33:34.099500Z

this is admittedly a bit painful to use without an alias, and aliases to namespaces that don't exist don't work, so you either need to do the create-ns / alias dodge or use a real namespace

If I require the analyzer as ana I can also go with ::ana/node.binding

Alex Miller (Clojure team) 2021-09-13T12:33:52.100300Z

(this is going to get better in 1.11 with lightweight alias support)

Alex Miller (Clojure team) 2021-09-13T12:34:02.100500Z

yes, that would work

Alex Miller (Clojure team) 2021-09-13T12:34:20.100800Z

although .'s in names are bad

I should put that on a sticky note

Alex Miller (Clojure team) 2021-09-13T12:35:40.102300Z

the problem is they have meaning understood in the compiler, which is: class name for symbols

Alex Miller (Clojure team) 2021-09-13T12:37:37.103700Z

the https://clojure.org/reference/reader#_literals spec actually says keywords "cannot contain '.' in the name part, or name classes"

πŸ‘ 1
Alex Miller (Clojure team) 2021-09-13T12:35:06.101900Z

so I'd either use node-binding or bite the bullet on moving it into the qualifier

Or cheat and create-ns for my own convenience, but that seems like bad form

Alex Miller (Clojure team) 2021-09-13T12:38:05.104300Z

well, you'd hardly be alone :)

hm πŸ€”

Alex Miller (Clojure team) 2021-09-13T12:38:23.104900Z

and like I said, better solution for this is coming

But it won't be backwards compatible

Alex Miller (Clojure team) 2021-09-13T12:39:03.105400Z

true

Alex Miller (Clojure team) 2021-09-13T12:39:37.106300Z

that is important here if it's part of analyzer.jvm

I'll try to be considerate in my implementation

It would be a tall order for it to be integrated into analyzer.jvm but having it as a companion library would be nice

While we're here, I'll also use this opportunity to ask regarding namespace and artifact naming convention (and copyright) A "correct" name for the spec's ns would probably be clojure.tools.analyzer.jvm.spec, but can I distribute a library whose root namespace is clojure ? Even if the license allows it, which I'm not sure it does, it feels rude

Alex Miller (Clojure team) 2021-09-13T12:49:06.110400Z

I assumed you were spec'ing this to include with tools.analyzer.jvm, where it would be fine

That was my intention, yes

Alex Miller (Clojure team) 2021-09-13T12:50:31.111400Z

seems like it would be the greatest utility to the most people as either part of that library or as a separate contrib lib

Alex Miller (Clojure team) 2021-09-13T12:51:21.111900Z

currently tools.analyzer still supports pretty old clojure versions, older than spec

So it would make sense as a contrib lib

Alex Miller (Clojure team) 2021-09-13T12:52:03.112600Z

I think so

Alex Miller (Clojure team) 2021-09-13T12:52:25.113Z

I'd have to ask Rich but I don't think he would object to that

Alex Miller (Clojure team) 2021-09-13T12:52:55.113500Z

we've taken the approach of appending ".specs" for stuff like this, so it would presumably be tools.analyzer.jvm.specs

If I'm doing it with the intent of it becoming a contrib library I should probably start with a tools.analyzer.specs then merge the jvm stuff on top of it

Alex Miller (Clojure team) 2021-09-13T13:03:39.115100Z

I mean, there isn't really anything but .jvm, so I think it would be fine to include it all in one

Alex Miller (Clojure team) 2021-09-13T13:04:32.115300Z

I can get a repo set up this week for you

πŸ‘ 1

What is the recommend route for evaluating the result of a clojure.spec.gen.alpha/check result summary set with the test framework (runners, assertions, and family) of clojure.test? The test.check library appears to have defspec, but it's unclear if it's a good fit to glue to spec/fdef generative, as examples seem to favor it's own clojure.test.check.generators in the guide examples.

Alex Miller (Clojure team) 2021-09-13T23:49:46.117900Z

I would recommend not doing that and separating your generative tests from your unit tests

i have something like the following:

(deftest specd-functions-pass-check
  (stest/instrument)
  (macros/filtered-check `a-namespace/a-function (num-tests 1))
  (macros/filtered-check `b-namespace/a-function (num-tests 3))

where macros/filtered-check is the following:

(defmacro filtered-check [sym opts]
  `(let [fltr# (or (.get (js/URLSearchParams. js/window.location.search) "filter") "")
         check# (fn [res#]
                  (cljs.test/is (= true (-> res# :clojure.spec.test.check/ret :result))
                                (goog.string/format "spec check failure:\r\n%s"
                                                    (with-out-str (cljs.pprint/pprint res#)))))]
     (when (or (= fltr# "all")
               (clojure.string/includes? (clojure.string/join "/" [(namespace ~sym) (name ~sym)]) fltr#))
       (let [check-res# (clojure.spec.test.alpha/check ~sym ~opts)]
         (clojure.spec.test.alpha/summarize-results check-res#)
         (if (nil? check-res#)
           (cljs.test/is false "stest/check result was nil. did you pass it any valid symbols?")
           (doall (map check# check-res#)))))))

^^ clojurescript obviously

(designed for figwheel-main autotesting)

also i don't write "unit tests" as they are subsumed by property-based tests

the "filtering" feature takes care of the "slow and don't run often" aspect

could you elaborate on this?

Alex Miller (Clojure team) 2021-09-14T00:53:27.118900Z

unit tests are generally fast and you run them a lot. generative tests are usually slow and you run them less often, they also often require most setup (for things like instrumentation, mocking etc).

Alex Miller (Clojure team) 2021-09-13T23:50:42.118600Z

some people have written code to wrap spec check tests into clojure.test, but I don't have that at hand