clojure-spec

Ben Sless 2021-09-13T07:04:23.079200Z

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

Ben Sless 2021-09-13T12:02:29.084100Z

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

vemv 2021-09-18T00:16:08.125Z

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)

Ben Sless 2021-09-18T04:43:31.125200Z

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
Ben Sless 2021-09-18T04:45:51.125400Z

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

Ben Sless 2021-09-13T12:03:51.085Z

But even before op, zooming in on binding

Ben Sless 2021-09-13T12:03:58.085300Z

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

Ben Sless 2021-09-13T12:04:13.085700Z

(imagine I merged it with ::common)

Ben Sless 2021-09-13T12:04:29.086100Z

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

Ben Sless 2021-09-13T12:04:42.086600Z

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

Ben Sless 2021-09-13T12:05:13.087200Z

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

Ben Sless 2021-09-13T12:05:38.088Z

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

Ben Sless 2021-09-13T12:06:06.088400Z

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

Ben Sless 2021-09-13T12:07:49.089800Z

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

Ben Sless 2021-09-13T12:10:31.092300Z

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

Ben Sless 2021-09-13T12:11:47.093800Z

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

Ben Sless 2021-09-13T12:12:26.094Z

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

Ben Sless 2021-09-13T12:31:40.096600Z

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?

Ben Sless 2021-09-13T12:32:18.097700Z

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

Ben Sless 2021-09-13T12:32:45.098300Z

ack

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

Ben Sless 2021-09-13T12:33:47.100100Z

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

Ben Sless 2021-09-13T12:35:14.102Z

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

Ben Sless 2021-09-13T12:36:56.102900Z

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

Ben Sless 2021-09-13T12:38:20.104800Z

hm πŸ€”

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

and like I said, better solution for this is coming

Ben Sless 2021-09-13T12:38:37.105200Z

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

Ben Sless 2021-09-13T12:39:48.106400Z

I'll try to be considerate in my implementation

Ben Sless 2021-09-13T12:40:21.107300Z

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

Ben Sless 2021-09-13T12:45:52.109600Z

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

Ben Sless 2021-09-13T12:50:13.111200Z

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

Ben Sless 2021-09-13T12:51:54.112400Z

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

Ben Sless 2021-09-13T12:53:53.113800Z

πŸ‘

Ben Sless 2021-09-13T12:54:25.114500Z

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
Franco Gasperino 2021-09-13T22:36:05.117400Z

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

johanatan 2021-09-21T20:02:45.131800Z

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

johanatan 2021-09-21T20:02:58.132Z

where macros/filtered-check is the following:

johanatan 2021-09-21T20:03:19.132200Z

(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#)))))))

johanatan 2021-09-21T20:03:28.132400Z

^^ clojurescript obviously

johanatan 2021-09-21T20:03:53.132600Z

(designed for figwheel-main autotesting)

johanatan 2021-09-21T20:04:40.132800Z

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

johanatan 2021-09-21T20:04:57.133Z

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

Michael Gardner 2021-09-14T00:05:06.118700Z

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