Bore da welsh_flag
good morning!
Morning! We have Clojure 1.13 Alpha 5 in production (all apps on one of our servers). Life on the bleeding edge π
I'm still unsure on how to use the new features, keys! etc., I get
And a bit of playing that I found helpful
(defn foo [{:keys! [a & :b]
:keys [c]
:as as
:or {c 42
;; d 100 ; contributes nothing
}
:defaults defaults
:select select}]
{:a-c [a c]
:as as
:defaults defaults
:select select})
(comment
[(= {:a-c [1 3]
:as {:a 1 :b 2 :c 3}
:defaults {:c 42}
:select {:a 1 :b 2 :c 3}}
(foo {:a 1 :b 2 :c 3}))
(= {:a-c [1 42]
:as {:a 1 :b 2}
:defaults {:c 42}
:select {:a 1 :b 2 :c 42}}
(foo {:a 1 :b 2}))])I'll have a review
(my mind isn't 100% there today, I think I'm coming down with a cold)
We're using both :keys! and :select at work (but not :defaults yet). Happy to answer any Qs.
Here's an example of :select from work code:
(reduce (fn [admins {:select row :keys [id & :email :isactive]}]
(conj admins
(assoc row :selected (admin-ids (str id)))))
[]
(jdbc/plan (:datasource db-spec)
[(str "select id, email, isactive"
" from adminUser"
" order by email")]))
Here we bind id because we need to use it in the function, and we list additional keys (not bound) for :email and :isactive, so row ends up with just those three keys.In this case, the plan call only produces those three keys, but if the query data came from somewhere else or used select * you couldn't guarantee that. Previously, this code used select-keys and (:id row) so :id was duplicated across those two, and the otherwise very simple conj/`assoc` code was "polluted" with select-keys making it harder to reason about.
What's the difference between doing a :select and just unstructuring, i.e., {:keys [id row]}....
Because you'd need to reassemble the map from the bound keys. And you'd need to bind all three keys:
(fn [admins {:keys [id email isactive]}]
(conj admins
(assoc {:id id :email email :isactive isactive} :selected (admin-ids (str id)))))Or you could have (fn [admins row] ..) and then do (select-keys row [:id :email :isactive]) (and still have (str (:id row))).
:select ensures you just get the keys you want, without having to duplicate the key list in a select-keys call.
(let [{:as data'
:select metadata
:keys [& :siteid :locale :title :metadescription :metakeywords]}
(-> data
(dissoc :name :title :content :excluded :noindex
:metatitle)
(assoc :title (:metatitle data)))]
In this case we get both the whole input map (`data'`) and the selected submap (`metadata`) and the keys are listed without being bound (because the body of the let only needs the map of selected keys, not the individual values.(and I could clean that up by not dissocing :title which would reduce that duplication a bitβthis code is already cleaner than it was before)
thank you, will read and review in a bit - just dealing with a production issue π
so select, is the map + defaults (and defaults are those that are defined with :or)
I played a bit more and I think the following example makes things clearer:
(deftest clj-1-13-destructuring-test
(let [input {:a 1 :b 2 :z 26}]
(is (= {:a 1
:b 2
:c 103
:select {:a 1 :b 2 :c 103}
:as {:a 1 :b 2 :z 26}
:as+defaults {:a 1 :b 2 :c 103 :z 26}}
(let [{:as as
:keys [a b c]
:or {c 103}
:defaults defaults
:select select} input]
{:a a
:b b
:c c
:select select
:as as
:as+defaults (merge defaults as)})))))
So :select gives elements from the input map that are mentioned in the destructuring form β not :z in the above example.
To get the whole input map + defaults you have to (merge defaults as) yourself, AFAICT.so, only those in the keys ...and those in the or (with or being seen as the defaults).
Yes, or the keys!. (IIUC)
Iβve asked about (merge defaults as) in #C03S1KBA2 at https://clojurians.slack.com/archives/C03S1KBA2/p1784805759634129
Did you like the answer you got? π
π
I think it would be useful to have a multi-level merge of :as and :defaults available (as didibus mentioned there).
I guess you just have to do it yourself β define the defaults separately and do a deep merge β same as ever.
(See example I posted in that other thread.)
Yeah, :select is nice in that regard. But when I looked at didibus' example, I was reminded about the joke where a guy goes to the doctor and says "Doctor, it hurts when I do this..." (very awkward and convoluted thing), and the doctor just says "Well, don't do that then!". πππ
but :select etc...
One use I found recently where :select would have come in useful was this bit of test code
(let [{:keys [body status] :select resp1} (http-request ...)
{:keys [body status] :select resp2] (legacy-http-request ...)]
(is (= resp1 resp2) "should be the same")
.... some other tests with body which we know now is equal)
Without :select I need to duplicate select keys or mistakenly use :as or binding body1 and body2 etc - this is more clear that we only care about the body and status not all the other info in the request.