Welcome! Please see the About page for a little more info on how this works.

+5 votes
in Collections by

I suggest adding :rest key for map destructuring syntax.
How do you think the following feature would be useful?

(let [m {:a 1 :b 2}
      {:keys [a] :rest r} m]
  (assert (= {:b 2} r)))

Or even like the rest of an array [a b & r]:

(let [m {:a 1 :b 2}
      {:keys [a]
       :& r} m]
  (assert (= {:b 2} r)))

2 Answers

+2 votes
selected by
Best answer

This does not generally make sense as maps are unordered (by default) and destructuring in this way would not have any guarantee that you'd get the [:a 1] kv first.

You can use sequential destructuring now though if you treat a map as a seq of kv pairs (with the caveat that the ordering is arbitrary), and then use associative destructuring on the first kv pair:

(let [m {:a 1 :b 2}
      [[& {:keys [a b]}] & r] m]
  (println a b r))
;; 1 nil ([:b 2])

But to reiterate, there is no guarantee here that [:a 1] is seen first, you could also get [:b 2].

You don't understand me correctly.
I wrote `[a b & r]` just to suggest `:&` keyword instead of `:rest` one.

The question was about getting a map *without* selected keys.
And map ordering doesn't matter.

Please look at the example: `(assert (= {:b 2} r)))`. It uses curly brackets not square ones.
Ok, well, I don't think we're going to do that.
Just out of curiosity, is there any reason why not? I have come across places where this could have been convenient to not have to dissoc later on, repeating the same keys.
0 votes

If you want to do it on your own, you could use macros to extend the language in your own library. This is a limited way, although it does not recursively walk the forms and fully replace let; you have to use it in lieu of let if you want to enable :rest forms. There are ways to code-walk and expand using macro tooling (like macrolet and other stuff), but that is an exercise for the reader. Quality Assurance notified me that they didn't have any time to test this outside of the immediate repl output you see, so user beware.

(defmacro fancy-let [bindings & body]
  (let [binds (partition-all 2 bindings)
        _     (assert (every? #(= 2 (count %)) binds) "binding form must be even yo!")]
    `(let [~@(->> (for [[l r :as b] binds]
                    (if (and (map? l)
                             (l :rest)
                             (or (l :keys) (l :syms) (l :strs)))
                      (let [ks        (l :keys)
                            strs      (l :strs)
                            syms      (l :syms)
                            remaining (l :rest)
                            selected  (concat (map keyword ks)
                                              (map name strs)
                                              (for [s syms]
                                                `(quote ~s)))
                            symb      (gensym "the-map")]
                        [symb r
                         (dissoc l :rest)  symb
                         remaining        `(dissoc ~symb ~@selected)])
                  (reduce (fn [acc xs] (apply conj acc xs)) []))]
user> (fancy-let [{:keys [a] :strs [name age] :syms [origin] :rest m}
                 {:a 1 :b 2 :c 3 "name" "bilbo" "age" 200 'origin :shire}]
                 {:a a :name name :age age :origin origin :remaining m})
{:a 1, :name "bilbo", :age 200, :origin :shire, :remaining {:b 2, :c 3}}