Fork me on GitHub
#clojure-spec
<
2016-12-19
>
naomarik18:12:51

I recall reading advising caution on using conform awhile back. Is this still the case?

bbloom18:12:49

are you sure it wasn’t caution on using conformer?

naomarik18:12:44

i can’t be sure

naomarik18:12:51

what’s the downside in any case?

naomarik18:12:13

for conformer, if that’s what it is

bbloom18:12:42

conformer enables your specs to do a little bit of data coercion

bbloom18:12:10

sometimes that’s useful to make a spec work, but it’s usually better to do that work in a function rather than a spec

bbloom18:12:35

ie just translate the data and spec the data that exists before and/or after that translation

bbloom18:12:47

that’s my best attempt as a short explanation, but others here can probably elaborate

naomarik18:12:28

okay cool, so there’s no problem with conform then in both the latest cljs and clojure right?

bbloom18:12:46

yeah, definitely not - conform is a non-trivial part of the utility of spec 😉

naomarik18:12:28

perfect, gonna save me a non-trivial amount of time writing destructuring branches on my multispecs 😉

seancorfield18:12:02

Paraphrasing Alex: if your spec also coerces data, bear in mind that this coercion will apply to all clients of that spec.

seancorfield18:12:30

At World Singles, we have a lot of specs that coerce string data to typed data. We’re OK with that 🙂

seancorfield18:12:21

Specifically, we have specs for our API calls that accepts strings as input and the result of conforming them is domain model values (so string -> long in some cases and string -> keyword in others).

seancorfield18:12:04

We generally write the specs for the domain model values first, and then write the API specs in terms of those (so an API spec is (s/and string? (s/conformer some-coercion) ::domain/spec-name) and we have a generator that fmaps the reverse coercion over the ::domain/spec-name (either str or name usually).

seancorfield18:12:23

We have some shorthand coercion specs tho’ (e.g., ->long which does (try (Long/parseLong s) (catch Exception _ ::s/invalid))).

naomarik18:12:58

i guess it’s extremely helpful when dealing with 3rd party APIs

bbloom18:12:20

i think what seancofield is saying, and by extension alex, is that conformer couples some interpretation of the data to the specification - however somebody else may want a different interpretation. so if you choose to use conformer, you prevent yourself and others from being able to apply a different interpretation

naomarik18:12:51

is this the reason to use caution and not that it’s in alpha?

bbloom18:12:53

if you don’t expect to ever need another interpretation, then go right ahead, but just know you’re making that trade off

bbloom18:12:01

for conformer specifically, yes

naomarik18:12:06

perfect thanks

bbloom18:12:49

seancorfield: the other option would be to not use conformer and then to do a secondary pass where you apply coercions, right?

bbloom18:12:24

i’ve been hacking on this thing: https://github.com/brandonbloom/ambiparse and i made a decision up front to include semantic rules as a primary feature - it’s basically the same idea as conformers

bbloom18:12:40

but parsing is different than validation - in parsing, you’re pretty certain to have a lot of “trivia” like whitespace etc that you want to discard

seancorfield19:12:36

@bbloom My objection to that is that you end up doing the work twice: once to determine whether a value is valid and then again to actually convert it — since you always want to convert it if it is valid.

seancorfield19:12:25

I mean, you could use a regex I suppose to say “It’s valid if it’s just digits and not longer than N” but that’s not the same as actually parsing a string to get a number — you’ll either allow too many digits or not enough.

seancorfield19:12:40

And if you decide to use a regex, now you have two problems 🙂

bbloom19:12:40

sure, the guidance is essentially “consider if you might want to NOT convert it in the future” - and if the answer is “nah, i’ll never need not do that” then go right ahead

bbloom19:12:54

there’s sorta a 3rd approach

bbloom19:12:20

which is what instaparse does: allow you to specify rewrite rules independently

seancorfield19:12:41

And it’s easy to write a non-coercing version of these specs: you just call str or name on the conformed result 🙂 (s/and ::api-spec/spec-name (s/conformer str)) 🙂

bbloom19:12:43

so like you’d have a map of specs to post-conformers

naomarik19:12:13

the amazement of discovering exercise is like using the repl for the first time

naomarik19:12:51

is there a way to pprint the output though?

bbloom19:12:16

you can just call clojure.pprint/pprint

naomarik19:12:50

yeah i think it’s fine, it’s just pulling out the common namespace part of the keywords but I guess this is normal behavior for pprint

naomarik19:12:38

like [#:firstpart {:next “blah"}] for things that are :firstpart/next

richiardiandrea19:12:37

@naomarik yes that's new syntax for namespaced keys in maps (namespaced maps?), I think it has been added in the first 1.9-alphas. They will all print that way

richiardiandrea19:12:07

ah ok the bot is not on 1.9 yet

richiardiandrea19:12:32

(get #:test{:foo 1 :bar 2} :test/foo)
1

kyle_schmidt21:12:04

Do specs belong with my application code or should I put them in their own namespace? Also, should I assert within application code or use the :pre and :post keywords to check my function i/o?

bbloom21:12:33

i asked the same question about where to put specs - it seems like both options occasionally make sense

bbloom21:12:07

it seems like if you have a single-namespace that wants to export some specs, go right ahead and put them inline

bbloom21:12:24

if you have a bunch of namespaces collaborating around some common stuff, maybe put the specs in their own namespace

bbloom21:12:32

start single-file and go multi-file if you need to

bbloom21:12:02

the only decision you really need to make up front is what namespace you want them to land in in terms of usage externally

bbloom21:12:51

there’s also a risk of a circular dependency between the spec namespace and the other namespaces

seancorfield21:12:38

@kyle_schmidt As for assert / :pre / :post — “it depends”.

seancorfield21:12:05

I would normally turn on instrumentation as part of my tests, and have certain tests that run check on key functions (ones that can be generatively tested). I generally do not have assert in any production code and only very rarely have :pre or :post.

kyle_schmidt21:12:58

Thank you for the replies. And just to make sure, is clojure.spec a replacement for clojure.test? Or would I still want to write both?

zane21:12:57

In my experience in practice you wind up writing both.

bbloom21:12:12

spec is definitely not a replacement for test

bbloom21:12:54

you use your specs from tests

bbloom21:12:04

at least that’s one use of spec

kyle_schmidt21:12:52

oh interesting. so use specs to aid tests

zane21:12:02

Ah, I interpreted that question as being whether clojure.test.check replaces clojure.test.

bbloom21:12:43

@kyle_schmidt: there’s a bunch of spec videos by stu halloway on youtube that are a good intro of actually doing some testing w/ spec

kyle_schmidt21:12:12

cool thanks I’ll check them out!

lambdahands22:12:52

Is there a way to spec out a function as part of a collection, such as a map? For example, if I define a spec like:

(s/def ::callback (s/fspec :args (s/cat :x number?) :ret number?))

(s/def ::foo (s/keys :req-un [::callback]))
Calling (s/valid? ::foo {:callback inc}) returns false

lambdahands22:12:57

I think in practice, I would typically just spec out the function I was using as the callback itself, and spec the key as just fn?. It seems simpler, but I wasn't sure if I was thinking about this correctly

bfabry22:12:38

@lambdahands I get true for that valid? call

Alex Miller (Clojure team)22:12:13

yeah, I would expect that to work

lambdahands22:12:24

Hmm. I am using clojure-future-spec – let me try with the latest alpha as well.

Alex Miller (Clojure team)22:12:25

(also ifn? is usually better than fn?)

lambdahands22:12:40

Ah, thanks for the tip @alexmiller!

Alex Miller (Clojure team)22:12:59

@naomarik you can also turn off namespace map syntax for maps if you want (set! *print-namespace-maps* false)

Alex Miller (Clojure team)22:12:41

it will default to false in Clojure but true in the repl

lambdahands22:12:22

Checked out that spec again, and it does work as you said. Strange, I must have been doing something wrong earlier as I was getting an unexpected result.