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
Thereβs not enough detail here for me to really answer that - how does a variant vary?
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
This ia clearly not for for development time, but for final artifacts it could be huge
this is probably a good use case for s/multi-spec
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
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?
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?
I think I would name them, and I would name them something related to the ast node type
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?
.'s should generally be used only in qualifiers, so I'd rule out the middle one
since these are ast nodes I would probably be trying to include either "ast" and/or "node" in the name of each one
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
"node" is not sufficiently unique - you should treat this as a global naming space
:clojure.tools.analyzer.node/binding
ack
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
(this is going to get better in 1.11 with lightweight alias support)
yes, that would work
although .'s in names are bad
I should put that on a sticky note
the problem is they have meaning understood in the compiler, which is: class name for symbols
the https://clojure.org/reference/reader#_literals spec actually says keywords "cannot contain '.' in the name part, or name classes"
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
well, you'd hardly be alone :)
hm π€
and like I said, better solution for this is coming
But it won't be backwards compatible
true
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
I assumed you were spec'ing this to include with tools.analyzer.jvm, where it would be fine
That was my intention, yes
seems like it would be the greatest utility to the most people as either part of that library or as a separate contrib lib
currently tools.analyzer still supports pretty old clojure versions, older than spec
So it would make sense as a contrib lib
I think so
I'd have to ask Rich but I don't think he would object to that
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
I mean, there isn't really anything but .jvm, so I think it would be fine to include it all in one
I can get a repo set up this week for you
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.
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?
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).
some people have written code to wrap spec check tests into clojure.test, but I don't have that at hand