This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2018-03-19
Channels
- # bangalore-clj (2)
- # beginners (217)
- # boot (3)
- # cider (130)
- # cljs-dev (117)
- # cljsrn (11)
- # clojure (99)
- # clojure-china (1)
- # clojure-denver (1)
- # clojure-dev (22)
- # clojure-italy (30)
- # clojure-norway (5)
- # clojure-russia (13)
- # clojure-sanfrancisco (3)
- # clojure-spec (74)
- # clojure-uk (107)
- # clojurescript (40)
- # clr (6)
- # core-async (25)
- # core-logic (4)
- # cursive (1)
- # data-science (1)
- # datomic (62)
- # duct (11)
- # editors (14)
- # figwheel (3)
- # fulcro (12)
- # funcool (1)
- # garden (12)
- # graphql (19)
- # jobs (4)
- # jobs-rus (1)
- # lein-figwheel (1)
- # leiningen (12)
- # luminus (5)
- # off-topic (45)
- # onyx (12)
- # other-languages (1)
- # parinfer (5)
- # programming-beginners (3)
- # re-frame (113)
- # reagent (63)
- # remote-jobs (10)
- # ring-swagger (1)
- # shadow-cljs (31)
- # slack-help (3)
- # spacemacs (27)
- # specter (1)
- # unrepl (44)
- # yada (16)
I have 3 specs describing a possible "identity" for an entity. I then have an "identity" spec along the lines of (s/def ::identity (s/or :domain/id1 ::id1 :domain/id2 ::id2 :domain/id3 ::id3)
. one of the specs can't use the built-in generator, how would I go about providing a custom generator that works for s/or
? I've managed to write some custom generators before for maps, s/and specs, but can't get my head around how to start writing one for this s/or case, and google hasn't yielded anything so far. Does anyone have any pointers pls?
I'm guessing I need to figure out how to use a combination of gen/tuple and gen/one-of :thinking_face:
(require '[cljs.spec.alpha :as s])
(require '[clojure.test.check])
(s/def ::id1 string?)
(s/def ::id2 pos-int?)
(s/def ::identity (s/or :id1 ::id1 :id2 ::id2))
(map first (s/exercise ::identity)) ;; => (1 2 "" 2 "" 2 "F91Q6p" "o6" "oet" "DE7")
(s/def ::identity (s/or :id1 (s/with-gen ::id1 (fn [] (s/gen #{"foo" "bar"})))
:id2 ::id2))
(map first (s/exercise ::identity)) ;; => (1 "bar" 2 2 2 15 31 "bar" "bar" 75)
@bbrinck Trying to keep generators in tests only, since I'm using minter.strgen/test.check stuff to help make the gen for "::id1" .
I've managed to get somewhere near what I need with (gen/one-of [gen1 gen2 gen3])
, but as mentioned in the spec guide, need it's a good idea to generate s/conform
like example for any s/or like specs: "For r specs that have a conformed value different than the original value (anything using s/or, s/cat, s/alt, etc) it can be useful to see a set of generated samples plus the result of conforming that sample data."
@mgrbyte I haven’t tried it out, but I believe you can use (require '[clojure.spec.gen.alpha :as gen])
and that will be OK in dev and test
(it lazy loads test.check
when generator is called, not defined, but presumably you won’t be invoking generators outside of tests)
Might simplify your implementation, although I don’t know if you want to keep generators in test for dependency reasons, or for other reasons like attaching different generators at different tiems
Hi. Did someone make a stab at spec bijections? Any lessons learned? Bumped into a actual need: need to uncoerce path-parameters in reverse-routing into string based either on the value type or on the defined spec.
With spec, I guess we could put both the encode & decode functions into spec metadata, something like:
(st/spec
{:spec keyword?
:description "a bijecting keyword"
::encode/string #(name %2)
::decode/string #(keyword %2)})
st/spec
? does that exist?
I've been talking about bijections a lot; I was thinking of setting up a generic library for composing bijections, that's not spec-specific
if you have ideas about how to tie them with spec, that would be interesting
that's a 3rd-party library?
my starting point is assuming that the most common use for bijections would be where you have some internal canonical representation of something, and you want to specify how to transform it to/from other "contexts", which could be things like json, jdbc, cvs, etc.
so when deciding how to integrate that with spec, one question that would come up would be whether it's worth automating the process of transforming the specs themselves so that you get context-specific versons of the specs, or merely to automate the process of writing the functions that take values in each direction
the downside of actually generating specs for each context is that it's more complicated and probably more work for the programmer the upside is I think you can get more features, like validating closer to the edge, and giving validation errors based on that representation rather than the internal one
s/keys is a big barrier; especially if you want to convert the keys to strings like in a json context
if you have that, you can’t have a different ::id
as the full key names are exposed outwards.
yeah you'd need a new namespace for each context probably
e.g., if I write a public JSON API and biject my internal spec to a JSON version of that spec, I can theoretically validate the user input using the json spec, before trying to transform/coerce the data to the internal representation
you can also then provide validation errors to the user in a way that's specific to the json representation, rather than the less helpful internal representation
here's what I came up with a few months ago; I did a proof of concept of integrating it with some code at work https://gist.github.com/gfredericks/358c39478f104281a6364f446f5b3c6b
oh, true that. Would mean multiple walks on the same model, but in most cases, the perf impact would not matter.
I've not seen it, other than I've speculated you could typecheck them in a dependently typed language
there is of course the relevant set theory stuff: https://en.wikipedia.org/wiki/Bijection,_injection_and_surjection
I've also wondered about separating the bijective parts from surjective parts (the times when you want to drop information, e.g., the order of keys in a query string)
if the surjections are formalized, you can use them to create generators of all the variant representations of something
super-interesting stuff, I think I should do some reading on this
thanks for the pointer! In my test, the transforming functions take two arguments: the spec and the value. This allows the transformation to extract information from the spec to do the transformation.
for example, stripping out non-defined keys from keys-spec needs the set of keys defined:
(defn strip-extra-keys [{:keys [keys]} x]
(if (and keys (map? x))
(select-keys x keys)
x))
I'd always approached that by building the function at compile-time
I should also point out this old effort in case it hasn't been seen: https://github.com/gfredericks/schema-bijections
I used that for real code at my old job, and it worked pretty good but made the code difficult to read
Validating before converting isn't any extra walks compared to validating after converting I don't think
Knowing full well this doesn’t make sense. How would you write a spec for a collection of maps where each maps keys were
1) unique
2) represents a value themselves
e.g
[data, data, data, …]
where data is something like {5 6}
well you would start with (s/coll-of (s/map-of int? int?))
each individual maps keys are (by definition) unique, but I assume you mean all of the keys over all the maps are unique
not sure what #2 means
for checking the uniqueness constraint, s/and another predicate that checks whatever property you need - something like #(apply distinct? (mapcat keys %))
> each individual maps keys are (by definition) unique, but I assume you mean all of the keys over all the maps are unique correct
you can't have duplicate keys in a map @drewverlee
I'm trying to write my own Spec protocol implementation. Is there somewhere where Spec and Specize protocols' contracts are explained?
no, because they are likely to change
@mgrbyte you can override generator just for ::id1 during exercise time
(s/def ::id1 string?)
(s/def ::id2 int?)
(s/def ::id (s/or :id1 ::id1 :id2 ::id2))
(map first (s/exercise ::id 10 {::id1 #(s/gen #{"foo"})}))
;;=> (0 -1 1 "foo" 0 -7 "foo" -1 "foo" 0)
I have:
(s/def ::key
(s/and keyword?
#(= "widget"
(namespace %))))
(s/fdef widget
:args
(s/cat :opts
(s/keys
:req-un [::title
::content]
:opt-un [::key
::icon
::widget-class
::settings
::collapsed?
::dropdowns
::controls
::preview?
::tabs
::collapse-opts
::help])))
but ::key
does not seem to get checked when it’s providedIn: [0 :key] val: :foo fails spec: :
Is there an idiomatic way of expressing a spec for “anything” other than (constantly true)
?
I’m trying to define a spec for a value that must ultimately be usable as a key in an associative structure.
I'm trying to pass a vector to s/keys
, but it keeps failing. Works great if I just write it out though. Something like this:
(def required
[:foo/city :foo/country :foo/street])
(spec/def :foo/form (spec/keys :req required
:opt []))
Hi guys, got a bit of a modelling problem. How do you build up specs with context dependent information.
E.g. Say you’re building a space for card details, say a map of the keys :card/number
:card/expiry
:card/name
:card/cvv
.
Now we want this to be a nice composable spec called :card/details
. But in some contexts :card/cvv
is required and in others it isnt. So The :card/details
spec is really a partial spec with requiredness of :card/cvv
dependent of context.
We might then say make two spec’s :card/details
and :card/full-details
. But this isn’t really scalable. It means whenever we have branching up the composition tree we need to rename things for all cases, giving an exponential explosion. Is there any way to build ‘partial’ specs where specs higher up the composition tree provide more or overriding data for specs they depend on.
Similar to dependent types.