Clojure.spec, maps, restrict valid keywords, easier way?

725 views
Skip to first unread message

Dave Tenny

unread,
Feb 2, 2017, 8:05:37 AM2/2/17
to Clojure
I want to specify in clojure spec that only declared keywords are permitted in function calls.
This is to catch what are usually mis-spelled or mis-cased keywors passed via option maps in function calls.

In the fdefs below, the second fdef will catch an invalid call, e.g.
(f 1 {:a 2 :B 3})
but the first fdef will not.

Is there an easier way to specify the more restrictive spec without having to add my own 'every' and enumeration of the keys?
(or build my own version of s/keys that does this automatically, which I'm guessing is the answer).

(require '[clojure.spec :as s])
(require '
[clojure.spec.test :as stest])


(s/def ::x (fn [x] true))               ;for lack of any?


;; Will not catch invalid keywords
(s/fdef f
 
:args (s/cat :x ::x
               
:options (s/keys :opt-un [::a ::b]))
 
:ret nil?)


;; Will catch invalid keywords, but is there an easier way?
(s/fdef f
 
:args (s/cat :x ::x
               
:options (s/and (s/keys :opt-un [::a ::b])
                               
#(every? #{:a :b} (keys %))))
 
:ret nil?)


(defn f [x {:keys [a b]}])


(stest/instrument `f)





Alex Miller

unread,
Feb 2, 2017, 10:07:31 AM2/2/17
to Clojure
We don't encourage you to do this, but I don't have an easier solution than this.

Dave Tenny

unread,
Feb 2, 2017, 11:27:09 AM2/2/17
to Clojure
On Thursday, February 2, 2017 at 10:07:31 AM UTC-5, Alex Miller wrote:
We don't encourage you to do this, but I don't have an easier solution than this.

Yes, and from the general standpoint of map handling I understand that.

From the standpoint of functions that take options and don't pass option maps through to other functions, I disagree with the clojure philosophy here.  So many bugs could be caught if we flagged unexpected map keys when they're used as options to functions.  Of course use and validation via clojure.spec helps too, but from a general bug catching standpoint I believe there's a huge value to flagging inputs to functions that aren't recognized by the functions. 

Mikhail Gusarov

unread,
Feb 2, 2017, 3:13:19 PM2/2/17
to Clojure

Hello Alex,

The idea is (as I understand it) that every function should accept any
map and pick keys which it understands. If some keys are critical, then
they should be marked as such in the spec.

Function might iterate over keys and raise an error if there are keys
which belong to the namespace function cares about, but the names are
not known, but it is outside of spec's functionality and ought to be
done manually.

Best regards,
Mikhail.

--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+u...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+u...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Matching Socks

unread,
Feb 2, 2017, 5:58:21 PM2/2/17
to Clojure
Keyword literals are inherently misspellable and trying to solve that problem with Spec does not really hit the nail on the head. But there is a solution: You do not have to use keyword literals very much! 

Instead of using, say, :egg/thunder throughout your program, def a var as :egg/thunder and then use the var.  Misspell the var and the compiler will bark at you.  If the var is egg/thunder then every part of the program can use it with not much loss of readability. 

If you mark the var as const, is it as efficient too as the keyword literal?

Alex Miller

unread,
Feb 2, 2017, 7:37:39 PM2/2/17
to Clojure
Ugh, don't do that. Introducing layers that add no value is a bad idea. Just use the keyword directly.

Timothy Baldridge

unread,
Feb 2, 2017, 7:49:20 PM2/2/17
to clo...@googlegroups.com
A good editor should auto-complete your keywords for you. Since using this feature in Cursive (same sort of thing is available in other editors) the cases where I've mis-spelled a keyword have dropped dramatically. It's a lot harder to mis-spell a keyword when you can just do: :egg/th<enter> and the rest is auto-filled. 

On Thu, Feb 2, 2017 at 5:37 PM, Alex Miller <al...@puredanger.com> wrote:
Ugh, don't do that. Introducing layers that add no value is a bad idea. Just use the keyword directly.
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to

For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+unsubscribe@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.



--
“One of the main causes of the fall of the Roman Empire was that–lacking zero–they had no way to indicate successful termination of their C programs.”
(Robert Firth)

Michael Gardner

unread,
Feb 2, 2017, 8:56:34 PM2/2/17
to clo...@googlegroups.com
What would be the Right Way to deal with typos like (fetch-important-data {:encypt true}), where the :encrypt key is optional? Timothy mentions auto-complete, which is better than nothing but doesn't feel like a real solution (especially to those who don't use auto-complete).

> On Feb 2, 2017, at 16:37, Alex Miller <al...@puredanger.com> wrote:
>
> Ugh, don't do that. Introducing layers that add no value is a bad idea. Just use the keyword directly.
>
> --
> You received this message because you are subscribed to the Google
> Groups "Clojure" group.
> To post to this group, send email to clo...@googlegroups.com
> Note that posts from new members are moderated - please be patient with your first post.
> To unsubscribe from this group, send email to
> clojure+u...@googlegroups.com
> For more options, visit this group at
> http://groups.google.com/group/clojure?hl=en
> ---
> You received this message because you are subscribed to the Google Groups "Clojure" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to clojure+u...@googlegroups.com.

John Schmidt

unread,
Feb 3, 2017, 5:47:21 AM2/3/17
to Clojure
I suggest something like (defn fetch-encrypted [] (fetch-important-data {:encrypt true})) + unit tests

Ben Brinckerhoff

unread,
Feb 3, 2017, 7:26:01 PM2/3/17
to Clojure
The eastwood linter can also be configured to look for possibly misspelled keyword typos

Linus Ericsson

unread,
Feb 3, 2017, 8:23:33 PM2/3/17
to Clojure
It would be great if an editor highlighted a (possibly qualified) keyword that was used only in that particular place (given all code loaded). This wouldn't be bullet-proof, but would have highlighted mistakes like :encypted (but could still confuse :encrypted? with :encrypted, and whatnot).

/Linus


On Friday, February 3, 2017 at 1:49:20 AM UTC+1, tbc++ wrote:
A good editor should auto-complete your keywords for you. Since using this feature in Cursive (same sort of thing is available in other editors) the cases where I've mis-spelled a keyword have dropped dramatically. It's a lot harder to mis-spell a keyword when you can just do: :egg/th<enter> and the rest is auto-filled. 
On Thu, Feb 2, 2017 at 5:37 PM, Alex Miller <al...@puredanger.com> wrote:
Ugh, don't do that. Introducing layers that add no value is a bad idea. Just use the keyword directly.

--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to

For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+u...@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

Colin Fleming

unread,
Feb 4, 2017, 3:03:53 AM2/4/17
to clo...@googlegroups.com
I'm actually planning to do exactly that in Cursive, and it's more or less what Eastwood does too per the link Ben posted.


For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+unsubscribe@googlegroups.com.

Dave Tenny

unread,
Feb 4, 2017, 6:48:19 AM2/4/17
to Clojure
I have found eastwood to be useful for a number of things, however on keyword checking it failed terribly on our code base, though perhaps there have been updates since I last tried it.

Thanks for the suggestion though.

Erik Assum

unread,
Feb 7, 2017, 5:55:07 AM2/7/17
to clo...@googlegroups.com
Bruce Hauman has done some work in this area both in figwheel and in a branch of leiningen. 

Basically, if I understand correctly, he looks for misspelled keywords in configuration maps by taking the levenstein distance between expected, valid, keywords and non-matching keywords in the configuration. 

Erik. 
-- 
i farta
Reply all
Reply to author
Forward
0 new messages