This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2018-04-12
Channels
- # beginners (47)
- # boot (5)
- # bristol-clojurians (1)
- # cider (45)
- # clara (2)
- # cljs-dev (11)
- # cljsrn (47)
- # clojure (169)
- # clojure-brasil (2)
- # clojure-dusseldorf (22)
- # clojure-finland (1)
- # clojure-italy (9)
- # clojure-nl (3)
- # clojure-poland (2)
- # clojure-russia (4)
- # clojure-spec (79)
- # clojure-uk (105)
- # clojurescript (59)
- # core-async (41)
- # cursive (31)
- # datomic (10)
- # devcards (1)
- # duct (6)
- # editors (9)
- # emacs (12)
- # figwheel (1)
- # fulcro (50)
- # java (4)
- # mount (1)
- # off-topic (47)
- # onyx (33)
- # pedestal (1)
- # protorepl (1)
- # re-frame (32)
- # reagent (45)
- # ring-swagger (6)
- # shadow-cljs (100)
- # tools-deps (6)
- # uncomplicate (27)
- # vim (3)
How would I spec a map like {:type akeyword :args dependsonkey}
?
s/multi-spec is designed to handle cases where you choose the spec based on the data (here the :type)
So that may be a good match here
I can use a multimethod to spec the map depending on type (multimethod methodname :akeyword [m] (s/keys [:args]))
Yes, that’s how s/multi-spec works
But I can't see at the moment how I would specify that :args
should have a different spec for that match
Like that it should accept string?
if :type
is :a
, but int?
if :type
is :b
Is :args actually unnamespaced?
nah, everything should be namespaced
When unsure how to spec something, it’s best to always return to how to represent the truth of your actual data. In this case you’re saying that you have one attribute that can have a variety of different structures
So you need to capture that in the spec
The spec for that attribute is A or B or C
You then have a separate constraint that says that a particular value of :type should co-occur with a particular form of :args and you should capture that constraint as a separate predicate
So you would model the attribute with s/or, the map with s/keys, and the constraint by s/and-ing the map and the constraint
Thank you. s/and-ing is always powerful, only problem is that we have to provide a custom-gen.
But usually I have to write custom-gens anyway...
(s/cat :args (s/* any?))
Is it possible to have a multi-spec
dispatch on the first value of a vector and return a Spec for the rest
of the vector? i.e. take [:my-vec 1 "2"]
. The multi-spec
would dispatch on :my-vec
and each defmethod
would return a spec for (vec (rest [:my-vec 1 "2"]))
- [1 "2"]
.
no, but you could dispatch on the first value of a vector and return a spec for the whole vector
Yeah... It's just the Spec for the first part of the vector is uninteresting -- it's always going to be any?
. This makes the return value for the defmethod
s very repetitive.
(defmethod event-vec :my-vec
[_]
(s/cat :x any? :a int? :b string?)
^^^^^^^
)
That part will always be the same.if only there was a way to remove boilerplate syntax….
oh wait, macros! :)
That's also possible. The problem there is that once I move that to a macro, I need to move all functions that register a method for that spec to be macros.
Would x always be any? You could have x be say #{:my-vec}
so the spec is more specific to the event spec you are returning?
The API consists of a lot of functions that look like this:
(defn reg-my-thing
[id spec other-stuff]
(defmethod my-multimethod id
[_]
(s/cat :x any? :rest spec))
;; do other stuff
)
In order to do what you're saying I'd need make most of the API macros. That isn't the end of the world but it does make the code base a lot messier to do what seems like such a simple operation.@kenny re “The problem there is that once I move that to a macro, I need to move all functions that register a method for that spec to be macros. ” - why?
(defmacro reg-my-thing
[id spec other-stuff]
`(defmethod my-multimethod id
[_]
(s/cat :x any? :rest ~spec))
;; do other stuff
)
I don’t think that macro is correct, but it can be fixed
this is a macro already, it’s just not the right macro
My API is defined a bunch of functions that are passed a spec for the (vec (rest [:my-vec 1 "2"]))
. The functions all do global registration sort of thing (akin to defmethod
). Each of these functions needs to register a spec for the whole vector [:my-vec 1 "2"]
. I could construct that spec at the macro level based on the spec they passed in, but that'd mean my whole API needs to be defined at the macro level.
... because this doesn't work 🙂
(defn reg-my-thing
[id spec other-stuff]
(defmethod my-multimethod id
[_]
(s/cat :x any? :rest spec))
^^^^
;; do other stuff
)
something like this works:
(require '[clojure.spec.alpha :as s])
(defmulti v first)
(defmethod v :hi [_]
(s/cat :o #{:hi} :p #{:there}))
(s/def ::v (s/multi-spec v (fn [val tag] val)))
(s/valid? ::v [:hi :there])
(defmacro defvspec
[op tail-spec]
`(defmethod v ~op [_#] (s/cat :op #{~op} :rest ~tail-spec)))
(defvspec :a (s/cat :x int?))
(s/valid? ::v [:a 100])
another option is to register your specs with s/def and then refer to them by their keyword name
which gets you out of caring about the form
if you have the spec instance, you can also have the macro invoke s/form to get back the form
in that case I don’t know that you even need a macro
(defn reg-my-thing
[id spec other-stuff]
(defmethod my-multimethod id
[_]
(s/cat :x any? :rest (s/form spec)))
^^^^
;; do other stuff
)
?(defn defvspec2
[op tail-spec]
(defmethod v op [_] (eval `(s/cat :op any? :rest ~(s/form tail-spec)))))
(s/valid? ::v [:b 10]) ;; true
(s/conform ::v [:b 10]) ;; {:op :b, :rest {:y 10}}
(s/explain ::v [:b nil])
;; In: [1] val: nil fails spec: :user/v at: [:b :rest :y] predicate: int?
really the same thing you’re doing with a macro
oh sure, throw that in at the end :)
Should've mentioned that in the beginning 😬 It's essentially adding Spec to re-frame, thus the reg-*
API.
well then, I don’t know :)
I don’t understand the constraints in cljs as well. There are some changes coming to spec that will help with stuff like this too but I’m not sure when or how they will play out in cljs.
I'm guessing everything will need to be done at the macro level. I think the only constraint is the lack of eval
.
If I have a list of lists, is it possible to write a spec that enforces that no two sublists start with the same value?
@mv Sure, if you can write a predicate that tests for that, you can use that predicate in a spec (or even as a spec).
Presumably you already have a spec for "list of lists"?
OK, so when you have your spec for list of lists, then you just s/and
that spec with your predicate and that's your complete spec.
(s/def ::list-list-spec (s/and (s/coll-of (s/coll-of ::sublist-element-spec)) sublists-have-unique-prefix))
(or something like that)
Bear in mind you may not be able to generate data from that spec (you might, but generation may produce sublists with identical first elements quite often so the check on the generated data might fail).
If that's important, you'll need to write a custom generator.
But if you're just getting started with spec, you may not need that. Yet 🙂