Fork me on GitHub
#clojure-spec
<
2018-01-18
>
kingmob15:01:39

In Clojurescript, what are people doing if they want to remove spec from production builds? Is there a lib for it? Macros wrapping fdef in the same file? Or placing all spec stuff in a dev-only namespace?

borkdude15:01:10

@kingmob just curious, but why would you want to remove them?

kingmob15:01:26

Download size

kingmob15:01:05

If I’m only validating/testing with spec, I don’t want it to take up sie in the resulting Js artifact

borkdude15:01:36

right. putting them in a dev only ns is what I would do then

kingmob15:01:49

Thx @borkdude. Thing is, I prefer fdefs be defined by their function, so I was hoping someone had already handled this. We have some dev-time-only logging macros I could repurpose… though I suspect dev-only namespaces would still be tricky, assuming it’s even possible

borkdude15:01:56

@kingmob this project has an example how dev only namespaces are possible (both lein and boot): https://github.com/borkdude/lein2boot

borkdude15:01:27

@kingmob you could also try in #clojurescript

dpsutton16:01:04

yeah it would be nice if you could elide all spec related stuff under advanced compilation

kingmob16:01:07

@borkdude Ahh, it looks like you factored out everything common into its own file and then use different :source-paths. We do that, too, but if, e.g., I added fdefs in animals.crud, they’d still end up in prod builds

kingmob16:01:41

We could add it to the dev source path, but then it’s separate from the definition…which is fine, it’s not really that big a deal

borkdude16:01:58

yeah, not getting around that when putting in a different ns

borkdude16:01:47

have you tried what difference in size you get for the production js with and without? it may not even be worth it

kingmob16:01:49

@dpsutton Yeah, it would be nice. Does spec use asserts under the hood so that the :elide-asserts option would remove it?

kingmob16:01:19

@borkdude Yeah, I have actually, it’s substantial 🙂 Lemme recompile and check with source-map-explorer again

borkdude16:01:35

I believe you 🙂

kingmob16:01:28

@borkdude OK, bare-bones spec is adding 39.84 kb to the output .js. It’s not major, but this is just for spec itself, and a sprinkling of initial spec defs. It’s not extensive at all, and since none of it is used at run-time, we might as well ditch it.

shaun-mahood18:01:06

I've been thinking about building some company/app wide specs. Say I spec something as :com.myco.app.service/id - is there any way that I can apply that spec to some data where everything is named as :service/id?

seancorfield18:01:29

(s/def :service/id :com.myco.app.service/id)?

seancorfield18:01:43

(and then use that local "alias")

shaun-mahood18:01:29

Oh I didn't think of that - thanks!

schmee20:01:09

is there something like this in spec?

(defn conform! [spec thing]
  (if (s/valid? spec thing)
    thing
    (throw (ex-info (s/explain-str spec thing)
                    (s/explain-data spec thing)))))

seancorfield20:01:56

Sounds like s/assert to me @schmee?

seancorfield20:01:31

Also, I'd note that your function does not conform the value so it's a very misleading name...

schmee20:01:58

sorry ’bout that, copy-paste error…

schmee20:01:06

but it sure does sound like s/assert, thanks 🙂

seancorfield20:01:50

Perhaps

(defn conform! [spec thing]
  (let [v (s/conform spec thing)]
    (if (s/invalid? v)
      (throw (ex-info ...))
      v)))

seancorfield20:01:41

(whereas s/assert returns the original thing, not the conformed value)

schmee20:01:25

yeah, I copied my conform thing from another project where I needed the conformed value, now I just want to validate so s/assert is perfect :thumbsup:

seancorfield20:01:14

Just bear in mind it can be turned on/off by command line options and by code (including 3rd party libraries!).

schmee20:01:12

hmm… using assert for config validation sounds kinda sketchy then

schmee20:01:55

ahh, putting it in :pre is what I want, forgot that it existed 😛

schmee20:01:52

no, that just gives an AssertionError, it doesn’t show you whats wrong….

seancorfield20:01:25

(and asserts can be turned on/off by calling code via *assert*)

uwo20:01:33

so, when s/keys fails on a missing required key, there’s no path information in explain-data. We’re attempting to dispatch on the result of explain-data, but the information we need about the missing key is in a :pred function. So is the only way to parse this data to parse the s expression used internally to describe required keys?

seancorfield20:01:40

@uwo Yeah, that's ugly. We wrote a heuristic-based explain-data walker to figure that stuff out. It recognizes and picks apart the :pred value...

uwo20:01:45

yikes. off the top of your head do you know if there’s jira ticket along these lines.

uwo20:01:09

thanks, btw!

seancorfield21:01:00

Off the top of my head, nope, sorry.

seancorfield21:01:17

But we needed to pick apart spec errors in general and map them back to our domain for reporting to end users... it just took us a while to flesh out the walker (it's... non-trivial).

uwo21:01:07

same for us

schmee21:01:58

how do you go about spec-ing anonymous functions with side-effects? since conform et al actually calls the function it’s validating, I guess the answer is “don’t do it?”

seancorfield21:01:05

Not sure what situation you're talking about @schmee?

schmee21:01:02

I have a piece of code that takes an array of functions, and those functions should return a particular type of record

schmee21:01:57

I call s/valid? on the input to this code, and if I use fspec to check the functions in the array, it will actually call all those fns to ensure that they return the correct thing

schmee21:01:37

but those fns could grab values from a database, launch nuclear missiles or whatever, so I’m thinking I should just spec it as (s/+ fn?) instead

schmee21:01:06

instead of (s/+ (s/fspec :args (s/cat) :ret (partial instance? MyRecord)))

seancorfield21:01:38

Yes, best not to use fspec for side-effecting functions-as-arguments because instrumentation uses generative testing on them.

seancorfield21:01:11

An array of functions sounds more like (s/coll-of ifn?)

schmee21:01:35

noted on ifn?, but why s/coll-of instead of s/+?

Alex Miller (Clojure team)21:01:15

+ is a regex op used for describing the internal structure of sequential collections (like an arg list). coll-of is for sequential collections. Either may be applicable here, depends on context.

Alex Miller (Clojure team)21:01:41

In particular, what if anything is including this spec

Alex Miller (Clojure team)21:01:54

If, for example, you’re including it as one arg in an args spec, using + would be bad as it would combine in the same sequential parent context

schmee21:01:41

I see, I’m using it as part of a s/keys spec so that shouldn’t be a problem then 🙂

Alex Miller (Clojure team)21:01:25

Either is prob ok then but I would prob use coll-of

Alex Miller (Clojure team)21:01:44

With a :min-count constraint

schmee21:01:11

I’ll go with that, thanks for the help :thumbsup:

schmee21:01:03

I’m a bit torn about the fspec thing though, I think it would be much more pleasant to see (s/fspec :args (s/cat) :ret (partial instance? MyRecord) in a doc-string instead of ifn?, but I can’t due to generative testing

schmee21:01:22

I can just write it out in the docstring anyway of course 🙂

schmee21:01:03

I’m still new to spec so I’m kinda feeling it out, it’s very easy to overdo it and use it as a type system

seancorfield22:01:12

Note that you can still run clojure.spec.test.alpha/check on your function with the full fspec -- independent of what the function's actual fdef says. (right @alexmiller)

seancorfield22:01:40

Or you could instrument it with a different fspec too I believe?

seancorfield22:01:48

I'd probably fdef it with a spec that is safe to instrument, and then check it with a more detailed fspec...

seancorfield22:01:29

(because it might, potentially, get instrumented by code far, far away in your code base that might "forget" to override the spec for it)

arohner23:01:40

Is there a library for an s/keys-alike but keys are strings rather than keywords, yet?