This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2016-06-16
Channels
- # arachne (1)
- # beginners (27)
- # boot (17)
- # cider (10)
- # cljs-dev (5)
- # cljsrn (76)
- # clojure (59)
- # clojure-austin (2)
- # clojure-brasil (1)
- # clojure-greece (76)
- # clojure-mexico (1)
- # clojure-quebec (63)
- # clojure-russia (16)
- # clojure-spec (127)
- # clojure-uk (12)
- # clojurescript (72)
- # community-development (7)
- # core-async (3)
- # core-matrix (2)
- # cursive (13)
- # datomic (8)
- # emacs (4)
- # funcool (4)
- # hoplon (148)
- # immutant (5)
- # keechma (2)
- # lambdaisland (5)
- # lein-figwheel (15)
- # leiningen (20)
- # off-topic (23)
- # om (13)
- # om-next (19)
- # onyx (11)
- # planck (11)
- # re-frame (59)
- # reagent (14)
- # rum (34)
- # specter (30)
- # spirituality-ethics (16)
- # uncomplicate (5)
- # untangled (387)
- # yada (2)
(t/check-var #’foo)
— it accepts a Var
.
Similarly (s/fn-spec #’foo)
— although the result of that is less useful to print since it is an object.
You could also do (t/check-fn foo (s/fn-spec #’foo))
check-fn
is so you can provide an (s/fspec :args … :ret … :fn …)
for an arbitrary function without fdef
.
Hi, all. I hacked up specs for most fns in clojure.core and 1/3rd of the macros (WIP). I don't know much about clojure.spec (of course), though, so it wasn't the good kind of hacking,. Hold your nose if necessary: https://gist.github.com/leifp/abe50082f6baa0063f8b7840e80657af
ahhhhh thanks @seancorfield
Shouldn't ((instrument #'my-fun) :foo)
throw an exception with an explanation if my-fun
has a spec defined for it with fdef
and if the returned value does not conform to the spec of my-fun
?
Oh, that's because that's what my-fun
returns in this case. So it doesn't even fail, it just returns its value, even if that doesn't conform to the function spec.
I see clojure.spec uses "named arguments" in several places (e.g. (clojure.spec/fdef :args ... :ret ....)
). Is there any convention as to when to use named arguments vs. a map? Maybe named arguments are primarily used for macros?
Hi, all. Has someone come across the need to have an s/keys
that is more strict (does not allow extra keys)?
@pithyless: I have. I am guessing Rich thought it isn't a good idea, as in the long term it could hurt expandability and composability. But I still think we should be able to do it in some cases.
I'm sure it's been brought up internally; I wonder how likely it would be to get an :only
option added to s/keys
.
@pithyless: some semi-related discussion here: https://groups.google.com/forum/#!topic/clojure/UVgXXcIxhJQ
As I suspected — "spec embraces the idea of open maps". But there are cases where you don't want open maps. A good example is the current world of lein-cljsbuild, where I regularly pull my hair out because it isn't clear if a particular setting is in the right place. Extra keys do no harm, so people tend to put config options all over the place, and you end up with a terrible mess. Figwheel recently started doing some great work towards fixing this — and this kind of config checking should really be "these keys, and these keys only".
I've got some half-baked solution with clojure.set/difference
and keys
, but I wish it were simpler.
I am hoping someone will write a validations library similar to bouncer, based on clojure.spec
.
Things are getting a little meta over here... I'm spec'ing out a data format and a parse function that first uses clojure.spec/conform
to validate and normalize the input and then passes it on to a transformation function. For that transformation function the input data format is the output of conform
, so I now have a spec for what conform
returns as well. 🙂
@ghadi: Thanks for linking to the archives. I understand why the default is what it is, my use case falls clearly in the "don't want to accidentally be leaking stuff" camp. But it's a minor thing... clojure.spec
is nice 🙂
@dominicm: oh 😞 I was hoping that one could build a validation system based on what explain-data
returns…
I currently use bouncer, but I have to work around its problems, and as I migrated from schema to spec, I was hoping I could get rid of it entirely.
@jrychter: When you do user registration validation, you need to check email uniqueness. So that requires 2 things: 1. Data to validate, the email 2. Sideband data, the database (connection). Spec has a funnel for 1. Not one for 2.
@jrychter: https://github.com/leonardoborges/bouncer/issues/43 😛 I know bouncer. It would be nice.
Your only options for validating in that way, and neither of these are particularly spec-y, are dynamic vars, or the weird macro I started working on, and gave up on because it was a big ball of mud.
Meaning, some of my bouncer validations actually use database or even (gasp) network connections.
As I recall, the discussion about closed maps ended with consensus that using select-keys
was appropriate for "not leaking additional keys", but there wasn't much consensus on validation of closed maps.
I got the impression @richhickey is "considering" adding support for it due to repeated requests from the community but he doesn't consider it a good idea.
I think designing systems to simply ignore extra keys is more robust, but I also accept there are cases where that is difficult (the example I gave is clojure.java.jdbc/insert!
where a map is converted to a SQL insert statement without reflection and therefore extra keys become extra columns and will be rejected by the database; the alternative is to introspect the database and ... well, do what? Silently ignore the extra columns? Throw a different exception?)..
And there in lies the issue: the behavior for extra keys is not knowable at that level - it would have to be an application-level decision (and the application has the means to do that reflection and call select-keys
if it wants).
Which is why I side with Rich that it's not clojure.spec
's job to directly support restricting keys, since it shouldn't be the norm, and you can do it via custom predicates already if you really have one of those odd cases where you really do need to check.
Goes back to my point that if you want that level of validation, just specifying that the db arg to insert! is a Connection or a Connectable or whatever is not sufficient; it would need to specify that the database in question has such and such a schema
Right, and that's a whole different thing. java.jdbc
provides metadata/reflection APIs but doesn't use them internally (for performance mostly),
A strict key set constraint can make a lot sense depending on the situation. This depends on the api of course, but I can't imagine a higher level api, that third parties are going to rail against, not having tighter key set constraints.
That being said, you can compose a strict map key set and description with spec fairly handily.
@pithyless: if you really want to get just the strict keys from a map, I would suggest using select-keys
as people mentioned before, as a plus, I wrote a simple utility that can extract the keys from a keys spec, it's still a naive implementation (it doens't consider req-un
or opt-un
) but can be starting point if you want to go further on the idea:
so I believe if extra keys are harmful on your case, you can use this kind of trick to remove unwanted ones, so we need to force the restriction of the keys, but just eliminate the ones you can't deal with (and only if you can't silently pass it on)
@wilkerlucio: The s/def
for ::name
and ::email
don’t do anything in that example…?
Are you doing something else to check the values of the ::my-aggregate
map conform to those specs, by convention (based on the key names)?
@seancorfield: the idea here was just to show an example of how to do a select-keys
while reusing the specs, you are supposed to call s/valid?
yourself before doing the strict-keys
on this case
OK, so you’re relying on convention that the (qualified) key names are the same as the specs that apply to their respective values?
but like I said, this is a naive implementation, if you really wanna rely on it, will need more work there
FYI, instead of (->> (s/form spec) (next) (partition 2) (map vec) (into {}))
you could just do (->> (s/form spec) next (apply hash-map))
@seancorfield: thanks for that, I'll update the snippet 🙂
has anyone come up with a good way of speccing non-keyword maps with known keyval pairs?
dev=> (defn f [x] (inc x))
#'dev/f
dev=> (s/fdef f :ret pos?)
dev/f
dev=> (s/instrument #'f)
#'dev/f
dev=> (f 1)
2
dev=> (f -3)
-2
the reasoning is that :ret and :fn are for checking whether the function is correct, which should happen when you're using the functions in clojure.spec.test. :args are for checking the function was invoked correctly
So, we have a function that reads environment variables. That function has a :ret
spec on it that validates that required environment variables are set and have valid values.
My understanding now is that instrument
will not help me here and I should use s/conform
?
yeah, s/conform or s/valid? or whatever explicitly. there's also an s/conform-ex coming iirc
looks like there was a commit 3 hours ago: https://github.com/clojure/clojure/commit/aa9b5677789821de219006ece80836bd5c6c8b9b
As mentioned before, I've (roughly) spec'ed a good chunk of clojure.core. I made that an actual repo in case someone wants to test and/or beautify them: https://github.com/leifp/spec-play
so, i'm new to test.check in general. anyone know how i might generate a set of keywords, from a known set of possible keywords?
e.g. i have #{:a :b :c :d :e}
and i want generated subsets of same
(s/exercise #{:foo :bar}) => ([:bar :bar] [:bar :bar] [:bar :bar] [:bar :bar] [:foo :foo] [:foo :foo] [:bar :bar] [:foo :foo] [:bar :bar] [:foo :foo])
interesting
(s/exercise (s/coll-of #{:foo :bar} #{})) => ([#{} #{}] [#{} #{}] [#{} #{}] [#{:bar :foo} #{:bar :foo}] [#{} #{}] [#{:bar :foo} #{:bar :foo}] [#{:bar :foo} #{:bar :foo}] [#{:bar} #{:bar}] [#{:bar :foo} #{:bar :foo}] [#{:bar :foo} #{:bar :foo}])
i'm modelling Magic the Gathering cards as an exercise
(s/def ::type #{:land :creature :artifact :enchantment :sorcery :instant :planeswalker})
(s/def ::types (s/with-gen
(s/and set? (s/+ ::type))
#( ? )))
anyway yeah s/exercise generates data that suits a spec, a spec of "sets of these keys" is (s/coll-of #{:keys} #{})
::types
works, but it can't be generated because of how s/and
generators work: generate for the first and discard anything that doesn't satisfy the rest of the ands
indeed, thank you
(s/def ::type #{:land :creature :artifact :enchantment :sorcery :instant :planeswalker})
=> :kafka-google-connector.runner/type
(map first (s/exercise (s/coll-of ::type #{})))
=>
(#{}
#{:planeswalker}
#{:artifact}
#{:land}
#{:creature :planeswalker}
#{:sorcery}
#{:land :planeswalker :sorcery}
#{:instant :enchantment :land :planeswalker}
#{:instant :enchantment :creature :land :planeswalker :sorcery}
#{:land :sorcery})
ok. my next question (which is the thing i really want to solve, now that i've softened you up 🙂 ) is how might i write a generator when i'm using s/and
on two s/keys
specs?
(s/def ::base-card (s/keys :req-un [::name ::types ::metadata]
:opt-un [::sub-type ::legendary? ::world?]))
(s/def ::cost string?) ;; todo
(s/def ::spell (s/and ::base-card (s/keys :req-un [::cost])))
ah 🙂 worth a try!
it's a super-interesting problem to solve
to me, anyway
@robert-stuttaford: just a suggestion, since you are modeling something new, maybe would be better to use the namespaced keys instead of clear ones, with namespaced keys you can for example validate a map keys even if you don't know the aggregate name for it
bhauman: What are the advantages of this vs. just (s/& (s/keys ...) #(only-these-keys % [:k ...]))
? Or a macro that expands into that.
In: [:there] val: :there fails spec: :howdy/fine at: [:there] predicate: #{:builds :server-port :server-ip :http-server-root}
@robert-stuttaford: I wrote this which works. I don't know how sane it is. my guess is "not very"
(defmacro extend-keys [spec-name & {:keys [req-un opt-un]}]
(let [spec-m (apply hash-map (rest (s/form spec-name)))]
`(s/keys :req-un ~(into (:req-un spec-m) req-un)
:opt-un ~(into (:opt-un spec-m) opt-un))))
=> #'kafka-google-connector.runner/extend-keys
(s/def ::spell (extend-keys ::base-card :req-un [::cost]))
=> :kafka-google-connector.runner/spell
bhauman: Hmm... I guess there is no explain
equivalent of conformer
or with-gen
, so I can't really think of another way to do it than reifying Spec. Your impl. looks fine, but it doesn't seem to explain the extra keys if one of the required keys fails its spec.