[POLL] Destructuring namespaced keywords

For destructuring a map with namespaced keywords we have a lot of options:

(require '[widget.factory :as factory])

;; direct
(fn [{clip :widget.factory/clip}]
  )

;; :keys + symbol
(fn [{:keys [widget.factory/clip]}]
  )

;; :keys + keyword
(fn [{:keys [:widget.factory/clip]}]
  )

;; same but with "auto-resolved keyword"
(fn [{:keys [::factory/clip]}]
  )

;; namespaced ::keys
(fn [{::factory/keys [clip]}]
  )

It’s often not clear which one to prefer, especially when destructuring a mix of un-namespaced and namespaced keywords. For example, say you have a map

{:title "paperclips"
 :created #inst "2018-01-01 10:11:12"
 :body "oh hello"
 :widget.factory/clip 42}

Which version would you prefer?

;; direct
(fn [{:keys [title created body] clip :widget.factory/clip}]
  )

;; :keys + symbol
(fn [{:keys [title created body widget.factory/clip]}]
  )

;; namespaced ::keys
(fn [{:keys [title created body] ::factory/keys [clip]}]
  )

;; namespaced keyword in ::keys
(fn [{:keys [title created body ::factory/clip]}]
  )
  • direct
  • :keys + symbol
  • namespaced ::keys
  • namespaced keyword in ::keys

0 voters

Discussion welcome. This is just one example, and depending on the exact things you’re destructuring I think people might end up with different choices.

One thing I’ve noticed a lot in the codebase I’m working on is that people will reach for namespaced keywords inside :keys, e.g. {:keys [foo bar :hello.world/baz]}. In this case a symbol would make more sense to me. On the other hand if there’s a namespace alias then using a keyword allows you to abbreviate things, {:keys [foo bar ::w/baz]}, so I think that’s where this tendency to use keywords comes from.

1 Like

I greatly prefer keywords over symbols here. For ease of grepping that key.

If n is a common varname, and I want to find all uses of :n, destructuring it as n leads to a world of pain. :stuck_out_tongue: So I’d do:

(let [{:keys [:a :b :c :a.b.c/d]} {:a 1, :b 2, :c 3, :a.b.c/d 4}]
  [a b c d])
=> [1 2 3 4]

That said, I’d also abbreviate :my.very.long/d (like ::long/d) because abbreviations tend to be pretty consistent. Hopefully. (And searching with a regex like :\S+/d should be manageable.)

I often use the ::keys shorthand to access keywords that “belong to” the current namespace anyways. So more often than not the only difference is :keys and ::keys.

I use {:keys [foo bar :hello.world/baz]}. Reasons why I do that (which may not be valid):

  • that’s how I learned it when the namespaced key syntax was introduced a few years ago
  • I think :keys [:foo] used to be invalid in earlier Clojure languages
  • inertia

The grepping argument cited above is interesting. It would indeed be convenient to be able to grep for :display-name.

The Clojure Guide on destructuring is also an interesting data point. They mention either using :keys+symbols

(let [{:keys [person/name person/age hobby/hobbies]} human] 
  )

or “auto-resolved keywords” (apparently that’s the correct term for these ::foo/bar)

(let [person {::p/name "Franklin", ::p/age 25}
      {:keys [::p/name ::p/age]} person]
  )
1 Like

I didn’t even know about the namespaced keyword destructure using ::foo/keys. I can imagine using that for destructuring maps with mixed namespaces in keywords, like

(let [{:keys         [collection]
       ::person/keys [name email]
       ::info/keys   [created-at]} 
      some-map]
   ,,,)
3 Likes

Yeah, this is really quite nice. It was introduced in Clojure 1.9.

Yeah this is new for me too, didn’t see it in the release notes but that’s a great change.

I’ve always relied on auto-resolved keywords.

1 Like

I’d like to chime in and say that I really dislike auto namespaced keywords (:: something). They look nice because you type less, but it means that the namespace of the keyword, which should only express the semantics of the domain, is now tied to the particular file/namespace where you used the keyword. It’s conflating domain organization and files organization which are totally unrelated. Good luck refactoring code when you decided to use this ::syntax, especially if you shared those keywords to the outside world.

That’s why I always use fully qualified keywords hence usually destructure with :keys + symbol. There’s a reason it’s the officially presented way of destructing in the guide, it’s because you should structure semantically, possible using different namespaces. The example cited above by @plexus shows this clearly : in the human map some keys have the namespace “person” while some doesn’t (“hobby”). The semantics matters above all.

Sorry if the tone looks harsh, it’s just that I’m typing on mobile which is really a PITA and I’d like to see this ::syntax disappear from all the code base I use :grinning:

2 Likes

You make good points. What I advocate around namespace-qualified keywords is:

  1. For internal use only (i.e., solely within the namespace), use ::foo, as if it were a “private” keyword,
  2. For any external use, put those keywords in their own namespace, separate from the code.

That second point extends to clojure.spec use built on those namespace-qualified keywords: the specs belong in that namespace too because they represent domain information.

The only functions I might have in that separate namespace are very lightweight utilities tied very tightly to those keywords (perhaps a few small predicates for specs).

4 Likes

I think we agree 100%. Private stuff makes sense, and namespaced keys are just too useful with spec.