7

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

Phil Lord
  • 2,917
  • 1
  • 20
  • 31

4 Answers4

6

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.

Cubic
  • 14,902
  • 5
  • 47
  • 92
  • My logic is that if they pass in the wrong keys, it is probably a mistake. I've used a map because it seemed a sensible enough option. Not sure what a better alternative would be. The function needs to take any number (including none) of a defined set of parameters. What data structure would you suggest? – Phil Lord Nov 03 '12 at 20:45
  • 1
    Of course a Map is still best in that scenario, I just still don't get why you'd limit yourself (or rather: your users!) to that. It's not necessarily that they're passing in the "wrong" keys but rather that they have a larger map of attributes that aren't necessarily made for your function alone. – Cubic Nov 03 '12 at 20:50
  • Ah, yes, that's true. I'm implementing what is effectively a DSL. So, the values, in this case, are likely to have been directly written by the user, rather than generated from somewhere else. So they are unlikely to have a large map of attributes. If they did, then they could always use select-keys to work around my restriction. – Phil Lord Nov 03 '12 at 21:59
  • Well, it's your library (or the library of someone you're working for). It's fine as long as it works for you. – Cubic Nov 03 '12 at 22:13
3

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}
nha
  • 17,623
  • 13
  • 87
  • 133
0

(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.

Mars
  • 8,689
  • 2
  • 42
  • 70
  • 1
    Nice idea, but it wouldn't solve my problem, because you are assuming that you have to pass all of the keys. I needed also to be able to pass only some of they keys, but not the wrong ones. In the end, I went a different route and didn't do this by destructuring since I needed a a much richer handling of the arguments. – Phil Lord Mar 18 '17 at 09:59
  • Ah, I understand. Perhaps this will help someone else. – Mars Mar 18 '17 at 16:33
0

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
akond
  • 15,865
  • 4
  • 35
  • 55