tl;dr
- When would I use a namespaced keyword instead of a bare keyword?
- When you need to avoid collisions and define grouping within a flat layout.
- When would I use a namespaced keyword vs. a fully qualified keyword?
- When the risk of collision even for normal namespaced keywords is too high so you need to include the full package name.
- When you need to encode lineage.
- What forms of keywords should my specs accept?
- Unqualified for the majority of cases, unless you have use cases that fall into the “When should I use a namespace or fully qualified package namespace”
Details
Most of the time, normal unqualified keywords are good enough.
This is because you’d generally have them in a map, which already seperates them by logical grouping.
(def user {:name "John", :email "jj@gmail.com"})
Since you’ve put the keywords inside a map inside the user variable, you know this map is for user data. And can use it like:
(:name user)
;;=> "John"
So in common usage, you rarely need to namespace the keyword itself, because they’ll already exist “within” something else that names the group they belong too.
Another reason is that within your own application, you don’t tend to have clashes in keyword names within the context they are used. For example, if a function names its arguments with keywords, it’s very rare you’d need two arguments to have the same name. Here’s an example:
(defn checkout
[user cart & {:keys [coupon sale gift?]}]
...)
(checkout user cart :coupon "1345" :gift? true)
This adds up to the fact that unqualified keywords are most common.
Now, where namespace keywords start to make sense is when names can clash or lineage matters.
One example of that is Clojure Spec.
Specs are registered in a global map and all specs are stored in the map in a flatten layout.
;; Simplified view for example purpose
(def specs
{:my.lib/user ...
:other.lib/user ...
:my.lib.user/name ...
:user/name ...})
Because of that, you can see that the layout is quite prone to name clashes, and it doesn’t have an inherent grouping. That’s why the use of qualified keywords is needed, to avoid collisions and to group the keys together (which the flat map layout doesn’t do).
In this scenario, the chance of collision is so high, that something like :user/name
still is at risk of collision, as you could imagine this being quite common. That’s why for specs you might want to actually use package + namespace name qualified keywords like :com.foo.lib.user/name
and such.
But because typing all that is annoying, syntax sugar was created in the form of ::user/name
which automatically expands the namespace to be the alias of the file namespace qualified location.
That said, this sugar turned out to be kind of confusing and accidentally coupled the keyword to its actual location in the source code, so if you were to ever move the keyword elsewhere (in a refactoring), your keyword namespaces would break.
What people really want is to create namespaces for their keywords to prevent collision, provide lineage and group related keys in flat layouts. That’s why in 1.11 they are most likely going to have a lightweight aliasing system that is decoupled from files.
On that topic, let’s discuss lineage. Now in some applications, keywords go beyond the scope of the app, it is sent to a database, or to some other service through an API, a file, etc. Now that can also create collisions, if say you were to store keywords globally between applications somewhere in a flat layout (like Datomic does). But even if you didn’t, you might want to know where the data came from, like sure this is a “user”, but which kind? Produced by what system? The namespace can be used for that as well: :com.foo.service-a.user/name
tells you it’s the user/name from service-a.
Finally grouping on flat layouts or non collated layouts, like if you have keywords in different places and want to join/group them?
{:user/name
:user/address
:shipping/address
:shipping/name}
Here the namespace allows you to avoid collision, but also group the name and address related to the user versus the ones related to the shipping.
If you had them seperate:
{:user/name
:user/address}
{:shipping/address
:shipping/name}
And wanted to join them, the namespace allows you to do so without collision and with preserving their grouping information even though they will be joined in a flat layout.
Ok, that’s all the use case I can think of. Now one last thing, because aliasing is cumbersome right now, and because programers are lazy, typing long namespace everywhere didn’t really become popular. So even when things leave the application boundary, people don’t always care about collisions if the system is still mostly under their control.
{:service-a.user/name}
{:service-b.user/name}
So a lot of people don’t fully put the full package, like the whole :com.foo...
in there and only add a small additional prefix.
Another issue of aliasing is they don’t support grouping on top of alias.
(require '[com.foo.app :as app])
;; This doesn't work, it doesn't become :com.foo.app.user/name
{::app.user/name "bar")
So you’re having to create an alias for every group, and that again is tedious, so again people tend to minimize the use of long package qualifiers.
Finally, another issue is that for keywords that leave the scope of your app, well, a keyword might be a bad choice of representation for things that leave your app, because keywords might not be friendly to databases, or to JavaScript front-ends, or to Java APIs, etc. Outside Clojure, it becomes tricky, what do you do with a namespace keyword? Most of the time keywords are mapped to strings in other languages, and strings don’t have the concept of a namespace, so where do you put the namespace?
Because keywords don’t map well to types in other non-Clojure systems, when a keyword is to leave your app boundary, it might actually be easier to use unqualified keywords.
For all these reasons, in the end I think beyond Specs which have a global collision problem and need a grouping mechanism since they have a flat layout, there wasn’t a lot of pros to namespaced keywords and there are too many cons, which is why most people still just use unqualified keywords for the most part.