Fork me on GitHub
#clojure-spec
<
2019-06-28
>
Daniel Hines10:06:52

(reposted from #clojure) I've got a problem I think spec would be perfect for, but I'm a total spec noob and have no idea how to spec it. I'm using pseudo set notation to describe the problem (read "m E S" as "m is a member of set S"). --- Given: There's a set of types, T, There's a set of attributes, A, There's a set of specs S There's a map such that every attribute has a corresponding spec from S. What is the spec for 2-tuples where: - The first element R1 is a vector of where every element e E T. - The second element R2 is a set of tuples in the form [e a v], where e E {1..length of R1}, a E A, and the value of v conforms to the spec corresponding to a. Any help is appreciated!

djtango13:06:47

so the first spec seems easy enough:

(s/def ::T #{:t1 :t2 ,,,})
You could map A and S manually by doing:
(s/def ::attribute-that-is-a-number number?
,,,
If you need to make the set of A to be concrete you'd probably want to look up the registry Then the final spec for your 2-tuple probably involves quite a hairy predicate (remember that all specs are 1-arg preds) This could then check that every first-element of the R2 is is a member of the set of ints from 1 up to (count R1) Then a spec that looks up value of a in R2 and does (s/valid? a v)

djtango13:06:56

hopefully that's enough to get the brain flowing

Daniel Hines13:06:38

Lol, why did I have to pick this as my first venture into specs 😛. Let’s try making it more concrete.

(s/def ::T #{:t1 :t2})
(s/def ::height number?)
(s/def ::name string?)
(s/def ::A #{::height ::name})
(s/def ::Goal (s/cat :R1 int? :R2 ???))

Daniel Hines13:06:00

… That’s all I got so far! Can I get a hint on the rest of the syntax?

Daniel Hines13:06:50

Maybe

(s/def ::Goal (s/cat :R1 int? :R2 (s/* (s/cat :e (set (range (count R1))) :a ??? v: ???)))

djtango14:06:58

R1 is a vector right? not an int

djtango14:06:16

if you don't think about ::Goal as a spec but more a function that takes the whole 2-tuple what would that function look like

Daniel Hines15:06:31

Yeah, you’re right, :R1 is more like (s/col-of ?int).

drone15:06:49

and ::R2 is (s/coll-of ::R2-elem :kind set?)

Daniel Hines15:06:18

Yeah, technically, t

Daniel Hines15:06:39

There’s more to it though, right @UCF779XFS? I need some predicate function that @U0HJD63RN was alluding to.

drone15:06:38

you’d need to a predicate that will take the R2/a and verify the R2/v passes s/valid?

drone15:06:06

so you’d have something like:

(defn valid-val?
  [[idx a v]]
  (s/valid? a v)) 

(s/and (s/cat _ _ _ _ _ _)
             valid-val?)

drone15:06:33

something like that/ where the empty cat is whatever you’re using for the three values in the R2 element

Daniel Hines15:06:53

Ok.. this is starting to come together!

drone15:06:06

take with a grain of salt, just woke up and may be stupid

djtango16:06:34

well from the description of the specification - R2's permissible values for e depends on R1

djtango16:06:37

so you need the whole 2-tuple

djtango16:06:02

so you would want a predicate that can compare R1 and R2

Daniel Hines16:06:21

Yeah, that's right.

djtango16:06:50

(let [[r1 r2] tup
     allowed-e? (->> r1 count inc (range 1) (into #{}))]
    (->> r2 (map first) (every? allowed-e?))
something like this maybe

Daniel Hines17:06:40

I’ll give that a shot later, thanks!

djtango17:06:09

so I guess you could split the spec for your tuple into three checks: That R1 is valid all e are valid wrt R1 all v conform to their corresponding a

Daniel Hines17:06:41

Yes! That sounds very doable

Daniel Hines17:06:36

I was hoping to get generators for free, but even besides this, there are other constraints that between entities of different types that are much too complicated without some constraint solver.

salokristian12:06:39

I'm working on creating a spec-based validator for creating human-readable error messages. I have a working implementation which locates errors based on the in key in the returned spec errors for explain-data. This works fine for errors concerning leaf-level specs, i.e. entries in a collection. However, I have not yet found a way to create useful error messages for custom predicates that concern a whole collection. Is there a (built-in or custom) way to return some sort of metadata from a custom spec predicate, which could be accessed in the spec error message? For example, the error message for this spec is not useful for locating the error, i.e. the duplicate values.

(s/def ::num int?)
(s/def ::list (s/and (s/coll-of ::num) #(apply distinct? %)))
(s/explain-data ::list [1 2 3 1])
I would like to be able to return some custom metadata from (apply distinct? %), which would tell me which elements caused the error, and allow to locate the errors precisely. Of course this is not just an error with this particular custom predicate, but all custom predicates. There doesn't seem to be a way to add metadata to the error messages generated by spec. Is this doable? If it isn't, what kind of a workaround would you suggest for this? I would like to use spec for checking correctness and generate errors based on spec errors.

salokristian06:06:35

I'm actually using phrase to generate end-user friendly errors. The problem is passing additional error-related metadata from custom spec predicates to spec errors (and therefore to phrase).

djtango09:07:27

I'm not convinced you can get helpful error messages from the general form of (s/def ::the-name #(apply distinct? %)) - if you consider that the spec is a one-arg predicate function, it would be hard to see how to determine which entry caused the failure. For the specific case of duplication, you'd probably need to replay the data or reconstruct it - as spec would only recall the input unless you walk it element by element. Even the inbuilt :distinct field in (s/every ...) doesn't seem to know which specific member breaks uniqueness

Daniel Hines15:06:46

Can test.check reverse regex specs out of the box?

nwjsmith15:06:41

No, you’ll need test.chuck for that

nwjsmith15:06:49

regex specs

nwjsmith15:06:58

I don’t think test.check is spec-aware

Daniel Hines15:06:36

Is there a built in spec for UUID’s that works in CLJS?

Daniel Hines15:06:45

Just need the strings.

nwjsmith15:06:22

Do you need a spec for string-encoded UUIDs, or would uuid? work?

drone17:06:53

there was a whole thread about that somewhere. a Google search would probably find it

drone17:06:12

uuid across JVM and JS

nwjsmith17:06:59

I think this will work:

(s/def ::uuid-string
  (s/with-gen
    #(re-find #"[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}" %)
    #(gen/fmap str (gen/uuid))))

nwjsmith17:06:14

On both platforms

Joe Lane17:06:38

Does that handle all UUID versions?

nwjsmith17:06:35

It should, but IIRC test.check.generators/uuid will only generate version 4 UUIDs

nwjsmith17:06:29

The textual representation of UUIDs is the same across all versions

Daniel Hines17:06:54

Thanks a bunch!

👍 1
Daniel Hines19:06:15

Are there any spec power tools for defining large entity maps? Not really sure what I’m looking for - just something more concise if possible.