This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2017-07-25
Channels
- # aleph (2)
- # aws (2)
- # beginners (37)
- # boot (23)
- # cider (29)
- # clara (34)
- # cljs-dev (2)
- # cljsrn (17)
- # clojure (230)
- # clojure-dev (47)
- # clojure-italy (11)
- # clojure-nl (2)
- # clojure-poland (5)
- # clojure-russia (52)
- # clojure-sg (1)
- # clojure-spec (70)
- # clojure-uk (73)
- # clojurescript (31)
- # core-async (9)
- # cursive (15)
- # datomic (39)
- # events (1)
- # graphql (1)
- # lein-figwheel (2)
- # luminus (13)
- # off-topic (2)
- # onyx (29)
- # other-lisps (1)
- # parinfer (15)
- # pedestal (14)
- # re-frame (41)
- # reagent (24)
- # ring (4)
- # ring-swagger (12)
- # rum (1)
- # spacemacs (3)
- # specter (1)
- # test-check (13)
- # timbre (9)
- # unrepl (29)
- # vim (5)
@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
that's a link to it
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]
I think s/cat
is suitable when you need to destructure seq into map, for example for clojure.pprint/print-table
s/cat
requires you to tag the elements which can be useful for conforming while s/tuple
doesn’t
@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
(defmacro +
"Returns a regex op that matches one or more values matching
pred. Produces a vector of matches"
[pred-form]
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]
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)))
something like (s/def ::datom (s/cat :e int? :a (s/? keyword?) :v (s/? map?) :t (s/? inst?) :added (s/? boolean?)))
?
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
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}}
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.
well, it’s great for some things, like when writing parsers and you want to dispatch on the tags
I did a toy macro to create maps which has some special syntax for not including nil values
first I wrote regular clojure code to parse it, but then I thought, why not do it with spec?
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}]
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.
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)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]
there is a currently undocumented s/nonconforming that you could wrap around the s/or for that
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
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)
hard to suggest an exact rewrite without knowing your goals re conforming
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
you don’t need a conformer
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
this is true. I can return [true :fsm/internal]
though, right? and write an unformer for such values
I would spec it as (s/def ::ft/internal? (s/or :b (s/nilable boolean?) :k #{:fsm/external :fsm/internal}))
the tagged result tells you how to coerce the value if needed
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?)
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
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.
@misha also you can check the spec-coerce project: https://github.com/wilkerlucio/spec-coerce
thanks @wilkerlucio
👋 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!
(I can try to reduce it down to a gist if anyone’s interested in seeing my slow ugly code)