Fork me on GitHub
#clojure-spec
<
2021-09-13
>
Ben Sless07:09:23

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)11:09:53

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

Alex Miller (Clojure team)11:09:20

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 Sless12:09:29

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

vemv00:09:08

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 Sless04:09:31

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

💯 2
Ben Sless04:09:51

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

Alex Miller (Clojure team)12:09:13

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

Ben Sless12:09:51

But even before op, zooming in on binding

Ben Sless12:09:58

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 Sless12:09:13

(imagine I merged it with ::common)

Ben Sless12:09:29

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

Ben Sless12:09:42

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

Ben Sless12:09:13

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

Alex Miller (Clojure team)12:09:38

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

Ben Sless12:09:38

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

Ben Sless12:09:06

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

Alex Miller (Clojure team)12:09:24

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

Ben Sless12:09:49

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

Alex Miller (Clojure team)12:09:38

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

Alex Miller (Clojure team)12:09:42

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

Ben Sless12:09:31

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 Sless12:09:47

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

Ben Sless12:09:26

something else entirely?

Alex Miller (Clojure team)12:09:44

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

Alex Miller (Clojure team)12:09:29

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)12:09:39

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

Ben Sless12:09:40

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 Sless12:09:18

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)12:09:18

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

Alex Miller (Clojure team)12:09:39

:clojure.tools.analyzer.node/binding

Alex Miller (Clojure team)12:09:34

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 Sless12:09:47

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

Alex Miller (Clojure team)12:09:52

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

Alex Miller (Clojure team)12:09:20

although .'s in names are bad

Ben Sless12:09:14

I should put that on a sticky note

Alex Miller (Clojure team)12:09:40

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

Alex Miller (Clojure team)12:09:37

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

👍 2
Alex Miller (Clojure team)12:09:06

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

Ben Sless12:09:56

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

Alex Miller (Clojure team)12:09:05

well, you'd hardly be alone :)

Ben Sless12:09:20

hm :thinking_face:

Alex Miller (Clojure team)12:09:23

and like I said, better solution for this is coming

Ben Sless12:09:37

But it won't be backwards compatible

Alex Miller (Clojure team)12:09:37

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

Ben Sless12:09:48

I'll try to be considerate in my implementation

Ben Sless12:09:21

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 Sless12:09:52

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)12:09:06

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

Ben Sless12:09:13

That was my intention, yes

Alex Miller (Clojure team)12:09:31

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)12:09:21

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

Ben Sless12:09:54

So it would make sense as a contrib lib

Alex Miller (Clojure team)12:09:25

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

Alex Miller (Clojure team)12:09:55

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

Ben Sless12:09:25

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)13:09:39

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)13:09:32

I can get a repo set up this week for you

👍 2
Franco Gasperino22:09:05

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)23:09:46

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

Michael Gardner00:09:06

could you elaborate on this?

Alex Miller (Clojure team)00:09:27

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

johanatan20:09:45

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

johanatan20:09:58

where macros/filtered-check is the following:

johanatan20:09:19

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

johanatan20:09:28

^^ clojurescript obviously

johanatan20:09:53

(designed for figwheel-main autotesting)

johanatan20:09:40

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

johanatan20:09:57

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

Alex Miller (Clojure team)23:09:42

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