Fork me on GitHub
#clojure-spec
<
2016-09-21
>
gardnervickers02:09:22

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.

gardnervickers02:09:14

Oh jeeze, missed that one thanks

hiredman02:09:20

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

bfabry02:09:50

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

bfabry02:09:06

that makes no sense

bfabry02:09:24

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

bfabry02:09:42

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

bfabry02:09:03

makes sense

hiredman02:09:12

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

bfabry02:09:49

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

hiredman02:09:05

oh, did I flip that around?

hiredman02:09:40

no, I think that is right

bfabry02:09:49

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

bfabry02:09:10

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

hiredman02:09:49

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

hiredman02:09:29

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

bfabry02:09:28

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)
:foo/bar
boot.user=> (s/valid? :foo/bar foo)
true
boot.user=> (s/valid? :foo/bar clojure.string/replace-first)
false

kestrel713:09:28

@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 "
  [n]
  (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"
  [n]
  (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)))

kestrel713:09:35

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

patrkris13:09:42

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?

patrkris14:09:07

@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

patrkris14:09:36

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.

patrkris14:09:47

Ehr... by "subnamespaces" I meant being able to say ::order.delivery/type without having a order.delivery alias created

Alex Miller (Clojure team)14:09:28

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

patrkris14:09:45

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

kkruit15:09:26

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...

minimal15:09:33

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

kkruit15:09:10

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

minimal15:09:05

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

kkruit15:09:59

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

kkruit15:09:07

thanks again!

minimal15:09:17

I didn’t notice it at first either

sparkofreason18:09:35

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

leongrapenthin18:09:07

@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

leongrapenthin18:09:59

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

leongrapenthin18:09:56

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

leongrapenthin18:09:09

So there is a good workaround

Alex Miller (Clojure team)18:09:17

yes that works fine now

leongrapenthin18:09:08

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

manderson19:09:44

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?

bfabry20:09:33

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

manderson20:09:29

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.

bfabry20:09:39

@manderson sorry I should've been clearer, define a spec called :my.app.person/name and a spec called :my.app.service/name or whatever makes semantic sense

manderson20:09:02

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

bfabry20:09:52

no worries

seancorfield21:09:37

@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.

seancorfield21:09:19

(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?

seancorfield21:09:50

(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)

stuartmitchell23:09:49

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

project-name.spec
                 -common
                 -thing1
                 -thing2
...
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

hiredman23:09:03

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

hiredman23:09:30

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

hiredman23:09:25

:: is a real pain when moving code around