Fork me on GitHub
#clojure-spec
<
2016-10-09
>
bensu07:10:49

I defined an fdef spec for a macro and I’m testing it with (is (thrown? Exception (macroexpand ‘(my-macro bad-args))))

bensu07:10:11

when I run it in the repl lein with-profile +test repl the test passes

bensu07:10:45

but when I run it with lein test the arguments are not checked and the test fails

bensu07:10:01

Has anybody hit a similar problem?

hiredman07:10:50

is your macro code actually being loaded? macroexpand doesn't throw an error if my-macro doesn't exist and isn't defined as a macro

bensu07:10:01

it was getting loaded

bensu07:10:48

the problem turned out to be related: it was loaded but not recognized by macroexpand because I was not using a syntax-quote

bensu07:10:59

@hiredman thank you for your help

Yehonathan Sharvit09:10:00

Sometimes s/conform and s/unform are not inlined. For instance:

(s/def ::my-spec (s/cat :op (s/? (s/cat :a integer?))))
(->> (s/conform ::my-spec '(1))
     (s/unform ::my-spec))
It returns ((1))

Yehonathan Sharvit09:10:45

There is an extra nesting

Yehonathan Sharvit09:10:29

Is it a bug in clojure.spec @alexmiller ?

sander12:10:42

is there a way to get a spec that (s/conform ::my-spec v) will always conform to? I want to fspec a function saying that it takes a spec-destructured value as an argument, and returns another spec-destructured value

Yehonathan Sharvit12:10:27

did you try s/any??

sander12:10:28

I should rephrase, I want a specific spec that only accepts values in the shape that (s/conform ::my-spec v) returns

Yehonathan Sharvit12:10:12

Interesting question...

Alex Miller (Clojure team)12:10:29

@viebel yes, generally anytime conform unform returns a different result, that's a bug. This particular one is already logged.

Alex Miller (Clojure team)12:10:11

@sander no, other than saying what you just said as a custom predicate - call s/valid? on the result with another spec

sander12:10:36

ok, should be easy enough, thanks!

Alex Miller (Clojure team)12:10:58

Rich and I talked about this one on Friday in particular - it's tricky

Alex Miller (Clojure team)12:10:06

Conceptually tricky about what an optional regex should return in different contexts, how it nests, etc

Yehonathan Sharvit12:10:03

In my specific case, I was able to workaround this bug using s/conformer. But I still don’t understand fully how s/conformer works. Could you explain in simple words how does s/conformer work?

Yehonathan Sharvit12:10:15

The docstring is a bit obscure...

Alex Miller (Clojure team)12:10:09

It acts as a predicate but applies an arbitrary transformation in the conformed value

Yehonathan Sharvit13:10:07

What is the simplest use case for conformer?

Alex Miller (Clojure team)13:10:46

Applying an arbitrary transformation to the conformed value

Alex Miller (Clojure team)13:10:53

It is primarily a tool for building more complex spec variants

Alex Miller (Clojure team)13:10:01

It's generally recommended to use sparingly as embedding it in a spec means that you are overriding how spec conforms for all future consumers of your spec

Alex Miller (Clojure team)13:10:21

Like if you used s/or but didn't want a tagged result

Alex Miller (Clojure team)13:10:31

When you do that you throw away info about how something was conformed

Alex Miller (Clojure team)13:10:51

One use might not need it but other possible future uses may

Alex Miller (Clojure team)13:10:09

So baking it into your public spec takes away that option

Yehonathan Sharvit13:10:14

I’ll play with that...

Yehonathan Sharvit13:10:36

@alexmiller another use case would be when one wants to match regex with vectors

Yehonathan Sharvit13:10:58

The naive code would be:

(s/def ::vec-of-int-and-str (s/cat :0 integer?
                                   :1 string?))

(->> (s/conform ::vec-of-int-and-str [1 "a"])
     (s/unform ::vec-of-int-and-str))

Alex Miller (Clojure team)13:10:03

There will be a different solution for that

Yehonathan Sharvit13:10:41

but it returns: (1 "a”) instead of [1 “a”]

Yehonathan Sharvit13:10:00

So the fix is:

(s/def ::vec-of-int-and-str (s/and
                              vector?
                              (s/conformer identity vec)
                              (s/cat :0 integer?
                                     :1 string?)))

Yehonathan Sharvit13:10:12

Is there a better solution for that?

Alex Miller (Clojure team)13:10:46

Not yet but it's coming

Yehonathan Sharvit13:10:11

I’m curious… ☺️

jrheard15:10:12

noob question, i’m sure, re: conformer - i reached for conformer when i was operating on data that looked like “123”, but i wanted (s/conform ::my-spec) to parse the integer such that it ended up being 123. what should i use instead of s/conformer in situations like that, for coercion of this sort?

jrheard15:10:26

i felt like i read about something that would solve this problem idiomatically in the rationale or guide, but couldn’t find it when i went back to look for it

seancorfield16:10:51

@jrheard: yes I have a lot of cases where I have strings as input but I want them conformed to integers (for API input specs). But @alexmiller has told me a couple of times that is "not idiomatic" and to avoid conformers like that :(

jrheard16:10:51

hm, i don’t understand - the strings have to be turned into integers at some point, right?

jrheard16:10:18

is it more idiomatic to have a secondary, non-spec transformation layer that happens after s/conform?

jrheard16:10:04

i’m sure @alexmiller has good reasons for saying that, but i don’t understand what spec users are actually supposed to do here

Alex Miller (Clojure team)17:10:41

The downside is that once you enshrine a conversion into your spec, you have made a conformance decision for all future consumers that has removed information. If you understand this ramification and are willing to live with the consequences, then that's up to you.

Alex Miller (Clojure team)17:10:34

There are many ways to apply conversions - you should be aware of when you are conflating conformance and conversion though

jrheard18:10:54

i think i follow those statements, but a “in situations like that, try doing this instead” example would be supremely helpful 🙂

Alex Miller (Clojure team)19:10:00

You can apply post transformations

Alex Miller (Clojure team)19:10:14

You can apply a second spec

Alex Miller (Clojure team)19:10:30

You can apply a different spec in one case vs@another

Alex Miller (Clojure team)19:10:30

And in some cases conformers might be the right answer