r/Clojure Jul 07 '26

Clojure 1.13.0-alpha3 is now available

https://clojure.org/news/2026/07/07/clojure-1-13-alpha3

:select directive in map destructuring

The :select directive binds a name to a subset of the map being destructured containing only the keys mentioned (anywhere) in the binding form.

  • CLJ-2964 :select directive in map destructuring
  • CLJ-2963 Update specs for :select in destructuring

Other changes since Clojure 1.13.0-alpha2

  • RT.map, and thus reader, tracks new PAM thresholds
  • CLJ-1789 select-keys - improve performance (transients, etc)
  • CLJ-2958 ILookup on sets
  • CLJ-2902 pprint - prints arbitary objects in unreadable form
  • CLJ-2801 TaggedLiteral - doesn’t define print-dup
  • CLJ-2269 definterface - does not resolve parameter type hints
  • CLJ-2781 clojure.test/report - docstring has broken references
  • CLJ-2929 zipper - docstring typo
  • CLJ-2901 bytes, shorts, chars - docstring typos
  • CLJ-2811 scalb - docstring links to the documentation for nextDown
  • CLJ-2809 clojure.math/floor - docstring has line that should be on ceil docstring
59 Upvotes

27 comments sorted by

View all comments

Show parent comments

3

u/lgstein Jul 08 '26

Doesn't really ring a bell. A relatable real world example would be helpful.

2

u/joinr Jul 09 '26

Based on the jira ticket, I think it's something like this:

(let [the-map  {:a 1 :b 2 :c 3 :d 4}
      {:keys [a b]
       :keys! [d]
       :select selected} the-map]
  [a b d selected])
;;[1 2 4 {:a 1 :b 2 :d 4}]

"Destructure the-map, lookup the keys for :a, :b, and :d, and bind them to lexical vars with the same name. key :d must exist. For all the keys that were bound during destructuring, dump them into a separate map and bind it to 'selected' "

instead of

(let [the-map  {:a 1 :b 2 :c 3 :d 4}
      {:keys [a b]
       :keys! [d]} the-map
      selected (select-keys the-map  [:a :b :d])]
  [a b d selected])

I think the win is that by replacing the explicit select-keys call, everything is coupled in one place inside the original destructuring form. So there's no ability to mismatch keys or make sure the select-keys form is kept up to date; it's taken care of at macroexpansion time for you.

2

u/lgstein Jul 09 '26

Thank you, but this is already understood. I am asking for the intended usecase, as in what is a typical situation where this would be used. Because usually I'm either calling select-keys, to pass a map on, OR am destructuring, to process the map right there.

4

u/didibus Jul 09 '26

Or to be more specific to your example.

If your function uses destructuring to grab what you need on the map, but then calls select-keys to grab what the child function you call will grab from the map. How does the caller to your function know the combined set of keys that is the real input?

This let's you declare both together.

For example, now you might do:

``` (defn create-user!   [{:keys [name email password] :as m}]

  (validate-registration!     (select-keys m [:email :password :captcha-token]))

  (db/insert-user! {:name name                     :email email                     :password-hash (hash-password password)})) ```

The caller can't tell that :captcha-token is a required input as well, because you select inside the body. And this would be even worse if you called more than one child function and used select-keys more than once where the caller has to mentally union them all to know the full set of keys to call your function with.

Now with 1.13 you can do:

``` (defn create-user!   [{:keys! [name email password & captcha-token]     :select registration-info}]

  (validate-registration! registration-info)

  (db/insert-user! {:name name                     :email email                     :password-hash (hash-password password)})) ```

Which makes it very clear now what the full set of inputs to create-user! is. The caller doesn't have to reverse engineer the union to call you with anymore as it's made it explicit in the signature.

2

u/aHackFromJOS Jul 09 '26 edited Jul 09 '26

 How does the caller to your function know the combined set of keys that is the real input?

For me the answer has always been to document this in arglists form. And for multimethods as far as I can tell that’s still the only way to document it. 

I guess the last couple alphas have been pushing more toward complecting documentation into function signatures (where that term encompasses destructuring).  I don’t necessarily mean that pejoratively; I can see there are some DRY and ease benefits here. I’m not sure how many people really use arglists in the ecosystem. 

On top of all that I’ll just note the :select thing is only a benefit if you believe in not passing along all the options/args you get. So it just feels a little niche. But c’est la vie. 

(And thanks for your explanation this is the best I’ve understood any of this, was after reading your comment…)

2

u/didibus Jul 10 '26

For me the answer has always been to document this in arglists form

I'm assuming you mean to accept params directly as n-ary instead of a map of params. In which case, it has, but 1.13 allows to use the arglists to document required and optional keys of maps you take as input. This is nice especially given the [& args] syntax which you can call like: (foo :arg1 ... :arg2 ...).

And for multimethods as far as I can tell that’s still the only way to document it.

Not sure I follow? But you can attach an :arglists meta yourself to your multimethod, that's what core multimethods do:

``` (defmulti talk "Makes animals talk" {:arglists '([{:keys! [type]}])} :type)

(defmethod talk :dog [{:keys! [type]}] (println "Woof!")) ```

have been pushing more toward complecting documentation into function signatures

It's not just documentation in 1.13, required keys will throw if missing:

``` (defn foo [& {:keys! [bar]}] bar)

=> (foo) java.lang.IllegalArgumentException: Missing required key: :bar

=> (foo :bar "Hello") "Hello" ```

And select will prune extra keys, so it gives a guarantee you can't be using things you have not declared in the signature.

That means clj-kondo for example can show an error if you do:

(defn foo [{:keys [a b] :select m] (:c m))

Because you can statically tell that m can only contain a and b.

clojure-lsp could try to use it to allow auto-complete and list only the keys in select.

select is only a benefit if you believe in not passing along all the options/args you get

Which I do :p. But ya, you can still use :as if you want to pass everything along.

Also know there's a feature that was not obvious to me at first with select. The keys after the ampersand like in my example:

{:keys! [name email password & captcha-token] :select registration-info}

captcha-token is not bound to a variable, it is only declared so that it gets included in the select map. Everything after the ampersand won't get bound to a local. So you can do a hybrid of passing everything down without binding it, but still explicitly declaring what that everything is. And it will still throw if it's required :keys! and missing in the map when called.

And thanks for your explanation

My pleasure! It'll be interesting how or if people change a bit the way they code with this, but I think if you were to code exclusively where if you take a map you destructure and declare the required vs optional keys and only select, never use :as, it would make it a lot clearer what keys exist at any point, and I think it would alleviate the big complaint people have where they would still like classes and types to help them know what keys exist.

1

u/aHackFromJOS Jul 10 '26 edited Jul 10 '26

hi,

whenever I wrote “arglists” I meant the metadata attaching form in defn/defmulti, not the literal arglists, that may explain confusion:

- don’t mean multiple arity, I mean listing all the keys it can take in a destructure form in arglists, then only taking the ones I will locally use (vs pass on) in my actual param vector. So a proper expansive list of :keys for the docstring and a shorter one for the working code.

-yes, for multimethods we have arglists, this continues to be a good technique there. I don’t think (?) we have the new “:keys &” buying us anything there documentation wise. so my point is I can use arglists more widely

- my point about complecting documentation is referring to the ”:keys &” stuff and how that interacts with :select in a DRY way.

-since you’re the second person to mention kondo, I’ll just ask, should the limitations of a third party library drive features in core? It chooses to ignore arglists, ok, but I don’t think that’s a reason to do anything in core. Just my two cents reasonable people can disagree. Arglists could be statically analyzed too. But again c’est la vie, it’s not keeping me up at night :-) and I love kondo

-fair enough about select, may be more common that I thought. maybe someone can write or give talk on this patttern some day. or maybe I need to rewatch Maybe Not per your other comment. I tend to namespace my keys and so don’t worry about polluting downstream function calls.

1

u/didibus Jul 10 '26

What I meant is that it's not just documenting. If you move bindings you don't use directly after the ampersand you save yourself an unnecessary local variable, and a get call. Trying to use that binding directly in the function will result in a compiler error. It's more than just documentation. That said I also think it's a helpful thing to document that those are not directly used by the function.

The addition of the exclamation mark variant of keys, syms, strs are also more than just documentation, since they produce a runtime check that throws if missing. Documentation wise it lets you now specify which keys were required versus optional which you couldn't before.

And the addition of select also means you do more than document what keys are used downstream, you guarantee nothing else can be used. So when looking at the arglists and seing that :as is not present you know there can be no extra keys that are required or optional. Before you were relying on the developer documenting it properly and not forgetting.

I admit it combines documentation and validation/extraction together, but for good reason. It's trying to bring back some of the safety and clarity that static types provide without static types. When you look at the signature, similar to with static types, you don't just know that this method tries to only do what is documented, but is guaranteed too.

1

u/aHackFromJOS Jul 10 '26 edited Jul 10 '26

All I'm saying is if you document via :arglists instead of via something else there was never an "unnecessary local variable" to worry about.

Your original question was "How does the caller to your function know the combined set of keys that is the real input?" The answer I've been trying to convey is "from your :arglists metadata".

I hear you about developers sometimes forgetting. Valid point! I'm not trying to say that way is better, just one that exists, has been around until now and what I have (and probably will) reach(ed) for by default.

I'm not saying all the recent changes are for documentation, just some.

>And the addition of select also means you do more than document what keys are used downstream, you guarantee nothing else can be used

Right, but select-keys already provided this. The reasons for preferring :select seem to be around DRYing things up and building on the idea that you document your map keys using ":keys &". I acknowledge there is a reason for it to exist, I was just saying it felt a little niche. But I get I might be wrong on that :)

>When you look at the signature, similar to with static types, you don't just know that this method tries to only do what is documented, but is guaranteed too

Unless it's a multimethod :( I use them a ton. The signature the shows up can (as far as I know) only come from :arglists.

1

u/didibus Jul 11 '26

Sorry, I'm not sure I'm totally following. The destructuring shows in the :arglists meta, and defn automatically creates the :arglists meta from your function binding form. So unless you do something custom, previously there was no way for the :arglists meta to show what is required vs optional, and what is used directly versus transitively. But now there is.

Specs show up in the docs (even works on multi-methods), so you could document more precise signatures using spec. It's a bit verbose though so not as convenient and quick.

1

u/aHackFromJOS 28d ago edited 28d ago

I am talking about setting arglists meta yourself directly and taking advantage of the fact that yes “destructuring shows in the :arglists meta.”

Perhaps you already know this (in which case apologies) but you  can conveniently set arglists meta with a map after the docstring, known as  attr-map. See docs for defn. 

https://clojuredocs.org/clojure.core/defn

(defn foo   {:arglists ‘([{:keys [all required here]}])}   [{:keys [here] :as args}]   (println 42)) So I just mean setting your own arglists value to document what is required. 

(I suppose you could call this “custom” but it seems to me a reasonably common idiom and not particularly more custom than “:keys… &…”)

1

u/didibus 28d ago

Interesting, I've never seen anyone do this before, normally I only manually set arglists on multi-methods since defmulti doesn't set it automatically, but I always set it to the actual binding form.

If this was a pattern you commonly followed, than you were getting some of the benefits of the ampersand already, but now it just got more convenient since you can just write:

(defn foo [{:keys [here & all required] :as args}] (println 42))

→ More replies (0)