1

In Clojure/Script the contains? function can be used to check if a subsequent get will succeed. I believe that the Clojure version will do the test without retrieving the value. The ClojureScript version uses get and does retrieve the value.

Is there an equivalent function that will test whether a path through a map, as used by get-in, will succeed? That does the test without retrieving the value?

For example, here is a naive implementation of contains-in? similar to the ClojureScript version of contains? that retrieves the value when doing the test.:

(def attrs {:attrs {:volume {:default "loud"}
                    :bass   nil
                    :treble {:default nil}}})

(defn contains-in? [m ks]
  (let [sentinel (js-obj)]
    (if (identical? (get-in m ks sentinel) sentinel)
      false
      true)))

(defn needs-input? [attr-key]
  (not (contains-in? attrs [:attrs attr-key :default])))

(println "volume: needs-input?: " (needs-input? :volume))
(println "  bass: needs-input?: " (needs-input? :bass))
(println "treble: needs-input?: " (needs-input? :treble))

;=>volume: needs-input?:  false
;=>  bass: needs-input?:  true
;=>treble: needs-input?:  false

Anything in the map of "attributes" that does not contain a default value requires user input. (nil is an acceptable default, but a missing value is not.)

5
  • Why do you ask for "That does the test without retrieving the value"? The expensive part is doing the key lookups; once you've done that, getting the value out is free. Commented Sep 6, 2020 at 18:27
  • @amalloy: Curiosity. It seems that Clojures implementation goes to an awful lot of bother and I just wondered why. Commented Sep 6, 2020 at 20:03
  • @Alan: The use case is pretty similar to the example but with deeper maps. One person defines a type of document, including characteristics it must have, should have, should not have, etc. Other people are tasked with writing those types of documents. The step illustrated here is to create a set of rules that can be applied to the documents as written by the second group. In addition to the validation, it should give useful error messages and guidance. Commented Sep 6, 2020 at 21:29
  • 1
    It feels like you're reinventing clojure.spec at this point...? Commented Sep 6, 2020 at 21:48
  • @sean: You may be right. I've never used it. I'll look into it. Thank you. Commented Sep 6, 2020 at 21:54

1 Answer 1

2
(defn contains-in? [m ks]
  (let [ks (seq ks)]
    (or (nil? ks)
        (loop [m m, [k & ks] ks]
          (and (try
                 (contains? m k)
                 (catch IllegalArgumentException _ false))
               (or (nil? ks)
                   (recur (get m k) ks)))))))

BTW, looking at the relevant functions in the source of clojure.lang.RT, I noticed that the behavior of contains? and get can be a bit buggy when their first argument is a string or Java array, as is illustrated below:

user=> (contains? "a" :a)
IllegalArgumentException contains? not supported on type: java.lang.String
user=> (contains? "a" 0.5)
true
user=> (get "a" 0.5)
\a
user=> (contains? (to-array [0]) :a)
IllegalArgumentException contains? not supported on type: [Ljava.lang.Object;
user=> (contains? (to-array [0]) 0.5)
true
user=> (get (to-array [0]) 0.5)
0
user=> (contains? [0] :a)
false
user=> (contains? [0] 0.5)
false
user=> (get [0] 0.5)
nil
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.