Fork me on GitHub

Hey folks, if I had a key in a map that I want to constrain to be a function that conforms to a spec, how would I go about that? I thought I would be able to pass the results of s/fdef to (s/def ::my-key …) but that does not seem to be the case.


Oh jeeze, missed that one thanks


I am sort of surprised that the result of fdef didn't work though

Alex Miller (Clojure team)02:09:09

It returns the result of def not fspec


ah, so you could use (s/spec ::my-key) @alexmiller ?


that makes no sense


so what does fdef define?

Alex Miller (Clojure team)02:09:48

Same as def but the key is a symbol not a keyword

Alex Miller (Clojure team)02:09:24

It registers, not defines

Alex Miller (Clojure team)02:09:37

Or at least that's how I think about it


oh, so you could use the symbol 'my-function in place of the spec? or at least (s/spec 'my-function)

Alex Miller (Clojure team)02:09:57

Yes - fully qualified though


makes sense


user=> (defn foo [x] x)
user=> (s/fdef foo :args (s/cat :arg any?) :ret any?)
user=> (s/def :foo/bar user/foo)
user=> (s/valid? :foo/bar foo)


@hiredman I think that will just use foo as a predicate


oh, did I flip that around?


no, I think that is right


like, (s/def :foo/bar user/foo) == (s/def :foo/bar clojure.core/identity)


buuut I think (s/def :foo/bar 'user/foo) would use the spec created by fdef


like regular def the thing being def'ed(registered) is evaluated


hah, I can tell that changed it because valid? takes longer to return because of the generative checking of functions


damn, so a lot of the core functions take a single arity it turns out, but anyway yes

oot.user=> (s/def :foo/bar 'boot.user/foo)
boot.user=> (s/valid? :foo/bar foo)
boot.user=> (s/valid? :foo/bar clojure.string/replace-first)


@alexmiller some feedback. I created a generator that did not conform to the spec (doh!). The generator contained the such-that predicate. When I tried creating a sample from the generator I got this error:

ExceptionInfo Couldn't satisfy such-that predicate after 100 tries.  clojure.core/ex-info (core.clj:4725)
I assumed that it referred to my custom generator but that was a red herring because in fact spec must be using such-that to ensure that the generated value conforms to the spec, and it was this such-that that generated the failure, not the one in my custom generator. Code (with the problem corrected but showing the such-that in my generator:
(defn mod11-checkdigit
  "Calculate the checkdigit see "
  (let [x (->> (map #(Integer/parseInt (str %)) (take 9 n))
               (map * (range 10 1 -1))
               (reduce +))
        y (mod x 11)
        c (- 11 y)]
    (cond (== 10 c) nil
          (== 11 c) 0
          :else c)))

(def nhs-number-gen
  "Generates a valid NHS number"
  (gen/fmap #(str (+ (* 10 %) (mod11-checkdigit (str %))))
            (gen/such-that #(mod11-checkdigit (str %))
                           (gen/choose 100000000 999999999))))

(defn nhs-number?
  "Returns true if passed a valid nhs number else returns false"
  (and (string? n) (= 10 (count n)) (= (str (mod11-checkdigit n)) (str (last n)))))

(s/def ::nhs-number (s/with-gen nhs-number?
                                (fn [] nhs-number-gen)))


It would be nicer if the error thrown due to the generated value being non-conformant with the spec stated this.


Say I have a namespace with two functions create-component-a and create-component-b. Each function takes a map with options. How would I go about spec'ing those option maps in the same namespace as the functions? Let's say that each map takes an :id (or something else) attribute, but they're semantically different. If I want to use the ::keyword shortcut to create a namespaced keyword, I can't call them both ::id. I could create a namespace for each type of option map, alias them and use something like ::a-options/id and ::b-options/id, but that seems wrong.

Alex Miller (Clojure team)14:09:13

@kestrel7 you are correct in your assessment - spec doesn’t trust custom generators and re-confirms they match the spec. Agreed that it would be nice to know the cause here more explicitly (kind of a special case of the general desire to know more about which part of a generator failed to generate). Happy to see a jira for enhancement.

Alex Miller (Clojure team)14:09:09

@patrkris your last suggestion is what I would do there (you don’t have to alias of course - you can just use fully-qualified too)

Alex Miller (Clojure team)14:09:14

why does that seem wrong?


@alexmiller: I don't know. Maybe it shouldn't 🙂. It's probably just from being used to define a schema (with herbert) in the same namespace as the function relying on it. But using the fully qualified version also feels a little wrong, because then you have hardcoded the namespace. It possibly just something I need to get used to


I thought about whether allowing the use of "subnamespaces" in aliases would be useful. For instance ::a-options/id without having an ::a-options alias. But I can imagine that it would turn out to be problematic.


Ehr... by "subnamespaces" I meant being able to say without having a alias created

Alex Miller (Clojure team)14:09:28

well, I don’t think we’ll do that :)


But it's not definite no? 😉 I'm kiddin'


Is there a way to require that in {:a "a" 😛 "b" :c "c"} :a is required and 😛 or :c is required and still have the other be optional? I've tried this using s/keys but cant figure out how i'd do it and I can't see how i could use s/map-of and still require :a...


(s/def ::mymap (s/keys :req-un [::a (or ::b ::c) ]))


oh man! I tried s/or... Thanks!


the or is syntax in the keys macro rather than clojure.core/or or spec/or


oh, I was just thinking. how is that working as clojure.core/or ... guess it's not haha


thanks again!


I didn’t notice it at first either


Some functions, like exercise, allow you to override generators by name. Is there any mechanism to do the same with conformers?


@alexmiller I think a good solution to aliasing keyword and symbol namespaces not existing in the codebase like :human.programmer/language would be (defalias hp human.programmer). So :hp would still be :hp, but ::hp/language would resolve to :human.programmer/language . This would overwrite aliases created via require. Should I write a tickent?

Alex Miller (Clojure team)18:09:50

the problem with it is that it requires the ns you are aliasing to exist

Alex Miller (Clojure team)18:09:18

we are already considering loosening that constraint or auto-creating the ns

Alex Miller (Clojure team)18:09:31

I don’t need a ticket for it


interesting. auto-creating seems like a good strategy

Alex Miller (Clojure team)18:09:20

that is by far easier given the existing implementation of Namespace etc


I just realized that I could use create-ns in combination with alias


So there is a good workaround

Alex Miller (Clojure team)18:09:17

yes that works fine now


DK about Cljs though

Alex Miller (Clojure team)19:09:07

have you looked at multi-spec?

Alex Miller (Clojure team)19:09:55

has that kind of a feel to me


I keep running into the same problem where I have two maps that have the same named key, but with different specs. For example: two maps that both have a key called name, where in the first it is a string?, but the second is int? because it is more of an ID. How do I specify this so that the specs can be different, but the field name the same in both maps? Only solution I see is to change name to something else (like person-name) and just living with the name change, but that doesn't work especially if modeling a domain I don't control. Thoughts?


@manderson :opt-un and :req-un in s/keys


Not sure I'm following... I still have to define a spec called ::name in two different ways. Yes, I could use or, but that muddies the definition since they are actually separate specs.


@manderson sorry I should've been clearer, define a spec called and a spec called or whatever makes semantic sense


Ok, so using the namespacing. That makes sense and should work for most of my cases (often translating from a format like JSON where there aren't namespaces). I suppose in the case where namespaces are expected, it would probably already be modeled correctly from the beginning (hopefully). Thanks @bfabry


no worries


@manderson To (hopefully) clarify: if you’re spec’ing with :req-un and :opt-un then you are using simple (non-namespaced) keys in your maps, but you can use a namespaced spec name / key name there to distinguish the two specs.


(s/def ::person (s/keys :req-un [:person/name]))
(s/def ::service (s/keys :req-un [:service/name]))
Both person and service specs are maps with a required :name key, but the specs used to validate those keys’ values will be different for ::person and ::service — does that help?


(it was something that I did not get on the first two or three reads of the Guide and it was Alex that set me straight on that)


Hi I am beginning a large multi-developer project that will use spec extensively I was wondering if there is any advice for how to place specs in namespaces. I was thinking of having a namespace structure like

and then requiring these namespaces in the actual code, probably when doing s/fdef below the defn this will require lots of map access to be ::thing1/id or ::thing1/date etc is this a good idea or should I be trying to keep all specs within the namespaces they deal with so ::id can be used when possible


tying your data model (names of attributes) to the way your code is currently organized seems like a bad idea


I would recommend picking good names for things in your maps, and sure namespace them, but don't think you have to use the same namespace names as your code


:: is a real pain when moving code around