couple nits on the PAM threshold change: 1. it's supposed to be only for maps of keywords, but the impl is not that strict: it only validates that keys 8..32 must be keywords, 0..7 can be anything
user=> (class (assoc {"0" 0 "1" 1 "2" 2 "3" 3 "4" 4 "5" 5 "6" 6 "7" 7} "8" 8))
clojure.lang.PersistentHashMap
user=> (class (assoc {"0" 0 "1" 1 "2" 2 "3" 3 "4" 4 "5" 5 "6" 6 "7" 7} :8 8))
clojure.lang.PersistentArrayMap
2. the reader doesn't respect the new threshold, so literals > 8 are still PHMs regardless
user=> (class {:0 0 :1 1 :2 2 :3 3 :4 4 :5 5 :6 6 :7 7 :8 8})
clojure.lang.PersistentHashMap1. Is not supposed to be only maps of keywords, that's an overstatement in the release note. It's behaving as designed, which is it's allowed to grow beyond 8 as long as all beyond are keywords. N.B. this is an implementation optimization, the details of which should never be relied upon!
2. Reader and other alignments are TODOs.
We have Alpha 2 in production for one of our apps. No noticeable changes in behavior at the JVM level, positive or negative. It's not our highest traffic app—we'll start that rollout on Monday.
(defn- service-start
[{:keys! [worldsingles-application members actioner database]
{:keys! [redis-pubsub] pubsub :redis-pubsub} :worldsingles-application
:as service}]
...)
Definitely a lot nicer than the destructuring + checking we were doing before! Unfortunately, clj-kondo/clojure-lsp don't understand the syntax yet so now I have red squiggles under uses of those bindings but I'm sure that'll happen soon 🙂 /cc @borkdude@seancorfield is keys! [& binding meant to signal you're not going to use the binding in choose-package, but somewhere downstream?
@felipecortezfi Yes, it allows for a single, coherent check at the top of a call tree, that says "these keys must be present", but allows clj-kondo (for example) to know that these are not unused bindings.
very nice!
Previously, we had explicit checks in the code, asserting the presence of such keys (using our own assert-ex macro that throws IllegalArgumentException rather than AssertionError).
ha, I remember you mentioning not wanting keys! to be backed by AssertionError during the dev call
And the non-binding form is also a useful improvement:
(defn choose-package
"Renders the html for choosing a package"
[{:keys! [& cookie-alternative promo-action] :as m}]
...)yes, I want this :)
A quick pass of our code at work: replaced dozens of destructure + key check code fragments with 30 uses of :keys! and a handful of & for non-binding key specifications. I suspect I could have been much more aggressive about that, and I'll probably default to :keys! going forward, and drop back to :keys for (what I hope are) the fewer cases where keys are optional.
The non-binding & form is proving much more useful than I expected! We had quite a few places where were asserting the presence of keys in high-level functions, but not using their values. There were also several places where we genuinely had unused key bindings that were provided for documentation purposes, and where the keys were a mixed of required and optional.
Thank you, core team!
@seancorfield initial clj-kondo support on master. @richhickey found an edge case I think:
user=> (let [{:keys! [& &]} {:& 1}])
nil
user=> (let [{:keys! [& &]} {:&x 1}])
nilin the sense that the spec may want to catch that one? not sure.
@borkdude we will preclude that, thanks