This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2017-07-10
Channels
- # beginners (15)
- # boot (15)
- # cider (6)
- # cljs-dev (231)
- # cljsjs (1)
- # cljsrn (26)
- # clojure (147)
- # clojure-argentina (1)
- # clojure-dev (8)
- # clojure-germany (1)
- # clojure-italy (26)
- # clojure-russia (2)
- # clojure-spec (83)
- # clojure-uk (154)
- # clojurescript (123)
- # conf-proposals (3)
- # core-async (5)
- # cursive (26)
- # datascript (21)
- # datomic (120)
- # emacs (2)
- # graphql (9)
- # hoplon (195)
- # instaparse (16)
- # jobs-discuss (1)
- # leiningen (8)
- # luminus (8)
- # lumo (7)
- # off-topic (17)
- # om (7)
- # om-next (3)
- # parinfer (121)
- # pedestal (5)
- # planck (13)
- # re-frame (11)
- # reagent (21)
- # ring-swagger (2)
- # spacemacs (28)
- # uncomplicate (3)
- # unrepl (7)
- # untangled (34)
- # vim (5)
Hiya folks, does this look like a valid spec? (s/def ::test (s/cat :k keyword? :rest (s/* any?)))
The reason I ask is I get different results with s/valid?
and an instrumented function
I.e. (s/valid? ::test [:foo]) => true
but when used in s/fdef test-fn :args (s/cat :t ::test)
it fails instrumentation
@anticrisis regex-style specs combine unless you wrap them with s/spec
as I recall.
So (s/fdef test-fn :args (s/cat :t (s/spec ::test)))
oh wow, ok
there's something I have a great deal of trouble wrapping my head around with these regexp specs I think
that does the trick, many thanks -- I will meditate on why, for a few hours.
If you do (s/exercise (s/cat :t ::test))
you'll see what's going on...
...it generates sequences and they conform to {:t {:k first-element :rest [other-elements]}}
Perhaps easier to see with this example:
boot.user=> (s/def ::pair (s/cat :a keyword? :b pos-int?))
:boot.user/pair
boot.user=> (s/def ::four (s/cat :ab ::pair :cd ::pair))
:boot.user/four
boot.user=> (s/valid? ::four [:x 1 :y 2])
true
boot.user=> (s/conform ::four [:x 1 :y 2])
{:ab {:a :x, :b 1}, :cd {:a :y, :b 2}}
boot.user=>
that part makes sense, but I'm not connecting it to the use of (s/spec)
to change the behavior of the s/*
operator when defining a function spec
I'm scrutinizing the output of s/exercise
with and without s/spec
right now
To me, the conformed output of (s/exercise (s/cat :t (s/spec ::test)))
and (s/exercise (s/cat :t ::test))
look the same
Thanks for tip about s/spec
with regexp ops and s/exercise
, they point me in the right direction, even if I don't completely understand it. Many thanks.
I still think there's something inconsistent going on, either because I'm misusing this simple s/*
form, or because something's broken. Here's a short walkthrough to demonstrate:
(s/def ::test (s/cat :k keyword? :rest (s/* pos-int?)))
(s/conform ::test [:foo 1 2 3 4])
;; => {:k :foo, :rest [1 2 3 4]}
(s/conform (s/spec ::test) [:foo 1 2 3 4])
;; => {:k :foo, :rest [1 2 3 4]}
(s/conform (s/cat :t ::test) [:foo 1 2 3 4])
;; => {:t {:k :foo, :rest [1 2 3 4]}}
(s/conform (s/cat :t (s/spec ::test)) [:foo 1 2 3 4])
;; => :clojure.spec.alpha/invalid
(defn test-fn [test] nil)
(s/fdef test-fn :args (s/cat :t ::test) :ret nil?)
(stest/instrument `test-fn)
(test-fn [:foo 1 2 3 4])
;; => exception
(s/fdef test-fn :args (s/cat :t (s/spec ::test)) :ret nil?)
(stest/instrument `test-fn)
(test-fn [:foo 1 2 3 4])
;; => nil
;; Note that in order to get test-fn to pass instrumentation,
;; its :args must use the same form which conform considers invalid
(s/conform (s/cat :t (s/spec ::test)) [[:foo 1 2 3 4]])
One element, of type ::test
.similarly your function accepts one argument, of type ::test
It's consistent @anticrisis
The :args
in s/fdef
is the sequence of arguments.
For your first s/fdef
-- :args (s/cat :t ::test)
-- your test-fn
would have to be (defn test-fn [kw & nums] nil)
-- first argument :k
, a keyword, and the :rest
of the arguments are numbers.
does that help @anticrisis ?
I see what you're saying... but I thought :args (s/cat :x ::whatever)
meant "the arguments are a list of one element, labeled 'x', which can be conformed with spec '::whatever'
Your example of fdef'ing [kw & nums] is helpful to tell me I've been thinking about this all wrong. I would have fdef'd it like this: :args (s/cat :k keyword? :nums (s/* pos-int?))
-- which you're telling me is the same as :args ::test
-- which it is, but those particular neurons in my brain apparently weren't connected.
For the record, I'd like to point out that nesting data structures is confusing. Is there a version of clojure I can use that doesn't do that? Thanks. š
If you want actual sequences (and nested sequences) then use coll-of etc, not cat šø
How can I stub a function? Typically Iād like to require that on-click
is a function, but it fails when I later exercise it since theres no generator for it
@hkjels would a custom generator of (gen/return (constantly nil))
be overkill?
@gfredericks ohh, hehe. no
Hmmm⦠Iāve just made an observation about specs which Iām wondering if others could validate for meā¦
Basically I have a layered architecture where we do something like this: (->> input transform-1 transform-2 transform-3)
and Iāve just realised that in layout terms it seems better to organise the input specs alongside the transforms, rather than the :ret
specs.
It also seems to more closely align with things like instrument
checking :args
rather than :ret
s
Has anyone else noticed this?
What is the most idiomatic way to spec something to be a single value?
Iām guessing:
(s/def ::single-value #{:foo})
mpenet: yeah the free gen is a big motivator š
(constantly :foo)
would work? That seems identical to any?
to me
it's a predicate that always returns truthy
#(= % :foo)
I guess?
just for keywords
I can imagine people stumbling on #{nil}
relatively often
use nil?
then
I guess that sort of error isn't likely to go unnoticed
The code is under change right now, but a preview of what Iām trying to achieve is in the tests e.g. https://github.com/bhb/expound/blob/master/test/expound/spec_test.cljs#L120-L137
Has anyone found a use case for specing a map with coll-of
? I see that you can create a spec like (s/def :foo/map (s/coll-of int? :kind map? :into {}))
but I canāt see why this would be useful since everything but the empty map would always fail.
I suppose you could maybe replace int?
in that example with a spec for the key/value pairs, but in that case, map-of
seems superior.
there are some cases where it is useful to spec a map as a sequence of entry tuples
āhybridā maps (combination of s/keys and s/map-of styles) is one case
example here: http://blog.cognitect.com/blog/2017/1/3/spec-destructuring
alexmiller: When I look to the result of (s/conform ::binding-form
it remember me about ast
's ... may in the future, can spec/conform be used as analyzer?
If you use that in the general sense, then yes absolutely - itās a great tool for turning s-expressions into a tree of maps describing a DSL syntax
@alexmiller Ah, I see. Youāre using a tuple
as the āinnerā spec. That makes sense. Thanks for the info!
thatās actually how s/map-of and s/every-kv are implemented
Interesting. Your example was instructive. I can see how or
ing together several tuples
would let me define different valid key/value pairs
Basically, it lets me create a clear relationship between the spec for certain keys and the specs for their values. But combine them all into one big map spec
Is fspec
meant to handle cases where the provided function throws? Itās causing spec errors when instrumented.
Iāve got a relatively minimal repro:
(t/use-fixtures :once #(do (s.test/instrument)
(%1)
(s.test/unstrument)))
(defn takes-fn [f]
(try (f true)
(catch Error e
false)))
(s/fdef takes-fn
:args (s/cat :f (s/fspec :args (s/cat :bool boolean?)
:ret boolean?))
:ret boolean?)
(deftest can-handle-throws
(is (false? (takes-fn #(if %1
(throw (ex-info "scary exception!" {}))
true)))))
That yields: In: [0] val: (true) fails at: [:args :f] predicate: (apply fn), scary exception!
when the test is run
when you have an fdef with an fspec arg, the instrumented var will check the fspec by generating from its args spec and invoking the function to validate it conforms to the spec
in this case it will generate boolean values and invoke f
specs cover non-exceptional use (as those are the exceptions :) so I think a more accurate fspec args spec in this case would use false?
, not boolean?
. it would then invoke f with only false and not trigger the exception
Right I guess I just wasn't expecting the fspec'd arg to be invoked? Wouldn't it be better to use with-gen
so the spec still captures the allowed range of values?
well the idea is that you said this function takes a function that responds a certain way - the only way to validate that is to invoke it
Yeah agreed. I donāt think that was really a question, I was just kinda surprised. I would have assumed that the fn would be instrumented and validated when it was invoked rather than when it was passed into another function. Might be something worth mentioning in the docs, if it isnāt already?
This has been brought up several times but I haven't had a chance to discuss it with Rich