Fork me on GitHub

@blance I was working on a library for managing generators separately from spec definitions a couple of months ago. If you’re interested, take a look at it!

👍 4

this looks promising! i'll definitely give it a try next time I write spec

😉 4

question about it seems like with data-spec there are now two namespaces: the one in the spec registry and the symbol i define in my own code. this makes sense with the one example in the readme because it is a map. but what about for code like

(def hit-box-spec
  (ds/spec ::hit-box [number?]))
It seems like both ::hit-box and hit-box-spec are now symbols that refer to a spec. I’m just getting a bit confused about how to use this library with non-map specs.


::hit-box is a keyword, specs resolve via a central registry that uses keywords. hit-box-spec is a var, containing whatever ds/spec returns (not necessarily the same value stored under that key in the spec registry, but maybe?)


yea. that’s how i understand it. i think i’m just confused why ds/spec doesn’t rely on the fact that it creates a side effect in the spec registry. i’m not sure why i’d want to also have a var in my namespace that points to a value that is apparently interchangeable (at least it is when using s/valid?


but it also points to the literal data,that ds/ functions can use to create other specs, right?


I'd assume that's the reason


I don’t think so. In the readme, they use a different var to point to the data used to create the spec. So they might write the above as (def hit-box-spec (ds/spec ::hit-box hit-box))


but it might have to do with these transformations, which I’m not using and don’t understand. that would make sense


hm what it is doing is more complex than this. my statement above that ::hit-box and hit-box-spec are interchangeable for valid? is wrong


@lee.justin.m side-effects are bad, I think the data-specs could benefit from a local-keys variant that doesn’t need to register the keys. data-specs are anyway not in align to the Spec filosophy of reusable keys, so why not go further down the road…


but for the original:

(ds/spec ::hit-box [number?])
can be written:
  {:name ::hit-box
   :spec [number?]})
… and as there are no keys, the :name can be omitted:
  {:spec [number?]})


to register that, you can say:

(s/def ::hit-box
    {:spec [number?]}))


@U055NJ5CC ah i see. i should be using the map syntax.


as for whether side effects are bad, that’s just the way spec works, right?


you have to register your spec with the registry


the thing i’m not getting right now is this:

(def banana-spec
  (ds/spec ::banana {:id integer?
                     :name string?}))
(st/registry #".*banana.*"))
=> ($banana/name$banana/id)


i see that the two subspec are registered, but why not the main banana spec?


if you try to put a map somewhere in the data-spec and don’t provide a :name, it will fail-fast:

  {:spec [[[[[[[{:a int?}]]]]]]]})
; CompilerException java.lang.AssertionError: Assert failed: spec must have a qualified name
; (qualified-keyword? n), compiling:(data_spec_test.cljc:380:3)


good question, not sure, maybe it should?


i just don’t even know how it works 🙂


basically i was expecting an unmunged given that i provided it that as the name of the spec. although i guess that’s consistent with the readme, now that i think about it. the only thing that got registered were the subspecs.


if there would be the local-keys, there would be no registering of any specs. that in mind, having a function called spec doing registration of the top-level would be bit odd.


there could be ds/def for that…


oh i see what you mean


… but (s/def ::name (ds/spec ..)) is the way to do it now.


because spec does (s/def ...) so we would expect it to cause a side effect


but here it’s just a function so we need to def it ourselves. okay. this is coming together for me.


I guess I thought there was a separate data structure for the specs. But they are just stored on normal vars?


the existence of the st/registry made me think this


all Specs are just values, you can store them in a Var.


(s/valid? string? "1")


(s/valid? (ds/spec {:spec [int?]}) [1 2 3])


.. unless you register them and get a name than can be used in s/keys. I think it’s the only one that requires a spec to be registered?


damn that is confusing 🙂


(s/valid? (s/coll-of int? :into []) [1 2 3])


basically, the thing that confused me is that if you do (s/def ::something ...) that will show up in the registry even it if isn’t meant to be used as a key but if you do (def something (ds/spec ...)) it doesn’t show up. but both work. i hadn’t appreciated the fact that nothing cares about the registry except for s/keys


btw, thanks for spec tools. i really really really prefer the self-documenting format of schema, and now i have my cake and eat it too


thanks! it’s kind of a roque lib, I hope the final version of spec will make much of it redundant.


what types of functions do you guys normally spec? do you spec utility functions?


the answer you’ll hear most commonly is “at data boundaries” or something like that. e.g. reading data from a file or network or moving from one chunk of code to another, rather than doing it wholesale on every internal function


I'd put it as "system boundaries" rather than "data boundaries" but yes, exactly that


where system boundaries are has a lot to do with your design, but if your system is designed it should have some :D


won't your system boundaries keep changing?


as you compose functions with functions


if your system boundaries are changing those weren't your system boundaries


another question! how do you define an fspec where you don't care about the name of the argument, but you care about the type?


ah thanks that sounds right haha


the idea is that it isn't a system if you don't define some limit or border, that's really the first step. What the boundaries are, and which things cross them, should be one of the first things defined, and often you'll want to define things so it changes relatively rarely


@ackerleytng for the names of things in specs, the reason to have the names is for the error messages you get without a match. Otherwise the output of a failure turns into a soup of data types that isn't very helpful.


oh! so it doesn't actually match against the name of the actual parameter?


if it is the thing I'm thinking of it's a series of name / type for each arg right?


the name is used in generating the message, it doesn't have to match the arglist or anything