Fork me on GitHub
#clojure-spec
<
2017-07-25
>
jpmonettas02:07:27

@bbrinck I remember yo were working on a lib for formatting clojure.spec errors, I've been also experimenting with the same but with GUIs

jpmonettas02:07:37

that's a link to it

bbrinck02:07:44

Looks great!

misha12:07:27

How do I spec various length tuples? For example datomic's datoms, which can be [e a v t op] [e a v t] [e a v] [e a]

misha12:07:57

s/or multiple s/tuples (or s/cats)?

misha12:07:44

btw, what are more suitable use cases for s/cat as opposed to s/tuple?

misha12:07:20

I think s/cat is suitable when you need to destructure seq into map, for example for clojure.pprint/print-table

schmee13:07:24

@misha use s/? for variable length tuples

schmee13:07:19

s/cat requires you to tag the elements which can be useful for conforming while s/tuple doesn’t

misha13:07:17

@schmee s/? s/+ expect homogeneous elements as far as I can tell reading docs. I, on the other hand, need to spec a seq where particular elements are of specific types

schmee13:07:46

not sure what that means, can you give an example?

misha13:07:41

(defmacro +
  "Returns a regex op that matches one or more values matching
  pred. Produces a vector of matches"
  [pred-form]

misha13:07:03

it'd be ok to use it to spec, say, seq of keywords. but I need to specify, seq of [int keywords type-a type-b bool] or [int keywords type-a type-b] or [int keywords type-a]

misha13:07:30

(basically each element can have its own spec)

misha13:07:06

I end up with or and tuple

(s/def :fsm/transition
  (s/or
    :6-tuple (s/tuple :fsm/from-state :fsm/to-state :fsm/trigger :fsm/guard :fsm/behavior :fsm/internal-transition?)
    :5-tuple (s/tuple :fsm/from-state :fsm/to-state :fsm/trigger :fsm/guard :fsm/behavior)
    :4-tuple (s/tuple :fsm/from-state :fsm/to-state :fsm/trigger :fsm/guard)
    :3-tuple (s/tuple :fsm/from-state :fsm/to-state :fsm/trigger)))

schmee13:07:10

something like (s/def ::datom (s/cat :e int? :a (s/? keyword?) :v (s/? map?) :t (s/? inst?) :added (s/? boolean?)))?

misha13:07:14

yeah, I might replace tuple with cat if I will require seq-to-map destructuring.

misha13:07:28

but in your example you need to wrap it in the s/or and add more "arities", because it'll fail validation on shorter seqs with Insufficient input

schmee13:07:50

nope:

dev=> (s/conform ::datom [1 :type])
{:a :type :e 1}
dev=> (s/conform ::datom [1 :type {:asdf 1}])
{:a :type :e 1 :v {:asdf 1}}

misha13:07:27

that's odd. how did I get "Insufficient input" then?

schmee13:07:53

can you post your spec?

misha13:07:11

I wiped it out already opieop

misha13:07:35

ah, lol, you wrapped each subspec in (s/?)

misha13:07:04

your spec probably will accept missing-element-specs too.

misha13:07:15

like [e a t]

misha13:07:47

which are not the droids I am looking for

schmee13:07:21

dang, you got me on that one

misha13:07:39

the only thing I don't like about or - those :6-tuple labels always feel like a hack. It's like I put effort into designing/naming specs, but those labels? just barf some random name out to make compiler stop complaining.

misha13:07:49

(or rather about me using or and similar specs)

schmee13:07:23

well, it’s great for some things, like when writing parsers and you want to dispatch on the tags

hmaurer13:07:07

can you give an example of that please? I am interested

misha13:07:15

when you conform, and then pass conformed value to any multimethod

schmee13:07:21

I did a toy macro to create maps which has some special syntax for not including nil values

schmee13:07:40

first I wrote regular clojure code to parse it, but then I thought, why not do it with spec?

schmee13:07:57

so the conformed input looks like this:

dev=> (spec/conform ::the-spec '(:a 1 :b 2 (maybe :c) nil [:d (pred-fn)] 123))
[{:k [:any :a] :v 1}
 {:k [:any :b] :v 2}
 {:k [:maybe {:k :c :maybe maybe}] :v nil}
 {:k [:pred {:k :d :pred (pred-fn)}] :v 123}]

schmee13:07:44

so you can see the :any, :maybe and :pred tags in there which come from s/cat specs

schmee13:07:01

which I then use to dispatch and parse to the appropriate form

schmee13:07:59

slack is acting up on me, excuse the double posts

hmaurer13:07:37

@schmee it’s acting up on me too; probably a server issue

hmaurer13:07:59

thanks for the explanation 🙂

schmee13:07:35

but in your case I agree it doesn’t add much value

misha13:07:16

I just have not had enough experience with spec to be actually using those tags much "later" in the code, so I don't really have naming intuition developed yet.

misha14:07:00

are there any apparent downsides of reusing spec names as dispatch keys in s/or/`s/cat`?

(s/def :foo/bar
  (s/or
    ::spec-one ::spec-one
    ::spec-two ::spec-two))
(apart from verbose error messages because of all the long qualified dispatch kw namespaces)

misha15:07:58

Is there a preferred way to coerce value during conforming? If it'd become or's dispatch value – it's fine. something like:

(s/def ::ft/internal?
  (s/or
    false #{false nil :fsm/external}
    true #{true :fsm/internal}))

(s/conform ::ft/internal? :fsm/internal)
;; => [true :fsm/internal]

Alex Miller (Clojure team)15:07:04

there is a currently undocumented s/nonconforming that you could wrap around the s/or for that

misha16:07:23

the exact problem I have in the s/or above is "Assert failed: spec/or expects k1 p1 k2 p2..., where ks are keywords", and I used booleans hoping to get this kind of coercion

Alex Miller (Clojure team)16:07:29

oh sorry, I didn’t even read your spec! the ks need to be keywords. And sets of falsey values won’t work (as they will return falsey values even when there’s a match)

Alex Miller (Clojure team)16:07:42

hard to suggest an exact rewrite without knowing your goals re conforming

misha16:07:43

I wrote a conformer for that:

(defn internal-transition? [t]
  (case t
    nil false
    false false
    true true
    :fsm/external false
    :fsm/internal true
    #?(:cljs :cljs.spec.alpha/invalid
       :clj  :clojure.spec.alpha/invalid)))

(s/def ::ft/internal? (s/conformer internal-transition?))

(s/conform ::ft/internal? :fsm/internal)   ;=> true
(s/conform ::ft/internal? :fsm/external)  ;=> false
(s/conform ::ft/internal? :foo/bar)        ;=> :clojure.spec.alpha/invalid

Alex Miller (Clojure team)16:07:29

you don’t need a conformer

Alex Miller (Clojure team)16:07:08

the problem with conformers is that you are doing a lossy conversion that throws away the original value, and making that decision for all future users of your spec

misha16:07:43

this is true. I can return [true :fsm/internal] though, right? and write an unformer for such values

Alex Miller (Clojure team)16:07:37

I would spec it as (s/def ::ft/internal? (s/or :b (s/nilable boolean?) :k #{:fsm/external :fsm/internal}))

Alex Miller (Clojure team)16:07:59

the tagged result tells you how to coerce the value if needed

misha16:07:17

I just wanted to avoid dispatching on arbitrary keyword later, instead of doing something like:

(->> huge-map ... ::ft/internal? first)
;;or just
(->> huge-map ... ::ft/internal?)

Alex Miller (Clojure team)16:07:05

I would at the very least write the spec as I have it above, then write a second spec that applies a conformer to the result of the first if needed

misha16:07:18

yeah, and so I'd need to

(->> huge-map ... ::ft/internal? coerce-internal)

misha16:07:54

just going through the options I have atm. Baked in conformer is indeed "bleh" comparing to just using coerce-fn there. I just don't really know how many client call sites there will be, don't want to forget calling coercion somewhere.

wilkerlucio17:07:51

@misha also you can check the spec-coerce project: https://github.com/wilkerlucio/spec-coerce

avi20:07:36

👋 Hi all! I’m new to spec and I’ve got a fairly complex fdef that’s taking a very long time to run… ~30 seconds. Are there any tips, articles, best practices, etc on debugging this sort of thing? Thanks!

avi20:07:12

(I can try to reduce it down to a gist if anyone’s interested in seeing my slow ugly code)

english20:07:51

@aviflax a gist might be useful. also, what’s taking a long time to run? is it stest/check?

avi20:07:03

Ah, yes, it is stest/check — thanks!

avi20:07:13

I will try to put together a gist that isolates the problem