Phil Lord
Phil Lord

Reputation: 3057

Destructuring Maps in clojure -- unused keys

I have defined a function which takes a map. I thought to use destructuring to access the values. However, I also want to check whether there are any used keys.

So, for example something like...

 (defun func1 [{:keys [a b c] :rest rest}]
    (println a b c)
    (println rest))

 (func1 {:a 1 :b 2 :c 3 :d 4})

which would print

 1 2 3
 4

The reason that I want this is that if rest is not null, this is probably an error, which I'd like to signal. I know about :as, which I could use. But then I need to store the list of valid keys twice.

Am I missing something?

Phil

Upvotes: 7

Views: 2192

Answers (4)

akond
akond

Reputation: 16035

I like how plumbing does that:

(use '[plumbing.core])
(defnk f1 [a b c & rest]
    (prn [a b c])
    (prn rest))
(f1 {:a 1 :b 2 :c 66 :z "fake"})

[1 2 66]
{:z "fake"}
=> nil

Upvotes: 0

Mars
Mars

Reputation: 8854

(Phil Lord, the OP has no doubt moved on to other problems, but here's a possible solution for anyone with a similar question.)

You could use count to test whether the map has the right number of keys:

(count {:a 1 :b 2 :c 3 :d 4}) ;=> 4

This returns the number of key/val pairs. As long as you're separately testing whether the map has the required keys, knowing that there are too many keys will tell you if there are any additional ones.

Upvotes: 0

nha
nha

Reputation: 18005

If you care about enforcing a structure to a given map, Schema may be a good choice (first example from the README) :

(ns schema-examples
  (:require [schema.core :as s
             :include-macros true ;; cljs only
             ]))

(def Data
  "A schema for a nested data type"
  {:a {:b s/Str
       :c s/Int}
   :d [{:e s/Keyword
        :f [s/Num]}]})

(s/validate
  Data
  {:a {:b "abc"
       :c 123}
   :d [{:e :bc
        :f [12.2 13 100]}
       {:e :bc
        :f [-1]}]})
;; Success!

(s/validate
  Data
  {:a {:b 123
       :c "ABC"}})
;; Exception -- Value does not match schema:
;;  {:a {:b (not (instance? java.lang.String 123)),
;;       :c (not (integer? "ABC"))},
;;   :d missing-required-key}

Upvotes: 3

Cubic
Cubic

Reputation: 15673

I don't really understand why you'd ever want to know if there are things that you don't care about anyways. If you're trying to do something like "do something specific with these keys, and do something generic with the others" you could do something like:

(defn func [& {:keys [a b] :as args}]
  (println a b c)
  (println (dissoc args :a :b)))

(func :a 3 :b :c 5) =>
  3 4
  {:c 5}
  nil

If you are paranoid about having to mention the keywords twice, you can probably do something about that too, but I can't imagine that it would be worth the bother.

The reason that I want this is that if rest is not null, this is probably an error, which I'd like to signal.

If you're that concerned about users passing in not exactly what you want, maybe a map isn't the right data structure to use.

Upvotes: 6

Related Questions