Fork me on GitHub
#clojure-spec
<
2017-10-21
>
James Vickers02:10:22

I'm trying to make a spec for a function that takes in a map with un-qualified keys (I know how to do this with qualified keywords, just use s/keys). The function arguments look like this: [{:keys [interest term balance] :as m}], and for that I make a function spec with (s/cat :m (s/keys :req-un [::interest ::term ::balance])) - I have specs named ::interest,`::term`, ::balance. Is there a more direct way to make a s/fdef spec for :args? I don't use the whole map (`:m`) in the function body and only added that part to use in the spec. I tried the example in the clojure.spec documentation for s/keys with unqualified keywords but couldn't get it to work. Alternatively, am I just way off by using un-qualified keywords as keys in the map the function takes?

taylor02:10:40

the :as m in the function argslist shouldn’t matter to spec, you should be able to remove it if you’re not using it

James Vickers03:10:17

Thanks for answering so fast. So s/cat needs key/pred argument pairs, so if I remove :m from the definition of the function spec, what would I put instead?

taylor03:10:08

the :m in your function spec doesn’t have any relation to the :as m in your arglist, it could be named anything really

taylor03:10:27

s/cat requires each “element” be tagged with some keyword though, so you can’t omit it. The actual keyword name doesn’t matter so much unless you’re interested in the conformed version of it i.e. it could be (s/cat :tgif ::map-spec) and it’d still work

taylor03:10:57

it looks like you’re doing it right

taylor03:10:54

if it doesn’t work when you remove :as m from the fn arglist, something else must be wrong

James Vickers03:10:17

You are totally correct - took out :as m in the function args list, still works. Can change :m to any keyword (e.g. :foo) in the :args spec and that works too. But that's so weird! In this usage, I guess spec doesn't use the keyword arg for this spec?

James Vickers03:10:00

That's weird. I wonder if there'll be other functions coming in spec like s/cat that don't have that setup, it seems weird that for this case (which seems common?), it doesn't matter what keyword you put there - just a placeholder. Thanks!

taylor03:10:46

s/cat does use the keyword to tag conformed outputs, it just doesn’t matter for your use case

taylor03:10:32

(s/def ::opts (s/* (s/cat :opt keyword? :val boolean?)))
(s/conform ::opts [:silent? false :verbose true])
;;=> [{:opt :silent?, :val false} {:opt :verbose, :val true}]
example from spec guide, notice the output has :opt and :val “tags”

James Vickers03:10:09

I guess the reason I didn't understand is I don't quite get s/cat and the other regex ops in spec.

seancorfield04:10:25

@jamesvickers19515 So your function has one arg, a hash map? (s/cat :m ::account) perhaps, with (s/def ::account (s/keys :req-un [::interest ::term ::balance]))

James Vickers05:10:05

Thanks @seancorfield. @taylor showed me that when using s/cat in this instance, the first argument actually didn't matter - could be :foo for all it mattered.

seancorfield05:10:50

Right, s/cat takes a sequence of (whatever) argument names and specs.

seancorfield05:10:17

It's convention to use the same (keyword) name for each argument as the function but there's no reason to.

seancorfield05:10:43

In your case, you have a destructuring as the first (only) argument so its name is somewhat arbitrary anyway.

James Vickers05:10:58

I guess what was surprising was that there wasn't a spec function for this case that doesn't require the unused keyword as the first arg - I was under the impression that functions that take a single map were common in Clojure.

seancorfield05:10:38

Yes... not sure what you're asking...

James Vickers05:10:55

Sorry, wasn't a question 🙂 just a comment

seancorfield05:10:06

(s/cat ...) is how you specify an argument list.

seancorfield05:10:11

Specs name things, as part of their conformance. The names don't have to correspond to anything in the source code (`s/or` is a good example, s/cat is similar).

James Vickers05:10:01

I think I sort of see now. I did something like this at the REPL:

(s/def ::account (s/cat :m (s/keys :req-un [::interest ::term ::balance])))
(s/conform ::account [{:interest 4.25 :term 360 :balance 261250}])
=> {:m {:interest 4.25, :term 360, :balance 261250}}

seancorfield05:10:20

Yeah, argument lists are sequences. s/cat matches a sequence of (named) specs. So, in this case :m is the name and the s/keys is the spec for it.

James Vickers05:10:27

And this call to valid? with a vector that looks like the function signature:

(s/valid? ::account [{:interest 4.25 :term 360 :balance 261250}])
=> true

James Vickers05:10:45

Thanks, I think I understand s/cat better after playing with it in the REPL a bit.