9
votes

I'm following this example: http://groups.google.com/group/clojure/browse_thread/thread/99b3d792b1d34b56

(see the last reply)

And this is the cryptic error that I get:

Clojure 1.2.1
user=> (def m {:a "x" :b "y" :c "z" :d "w"})
#'user/m
user=> (filter #(some % [:a :b]) m)
java.lang.IllegalArgumentException: Key must be integer
(user=>

Also I don't understand why this would even work. Isn't (some ...) going to return the first matching value, "x", every time? I'm a total noob at clojure and just trying to learn.

Please enlighten me.

3
The code from the ggroup thread you link to solves a different problem: "given a collection of maps and a set of keys, return a collection of precisely those of the given maps which contain at least one of the given keys". Thus, in this code, filter is meant to operate on a collection of maps, not a single map; and this particular #(...) block involving some is only appropriate if the given maps do not contain nil or false values (as mentioned in the thread), but in any case, the function it returns will be applied (lazily) to each of the given maps in turn. - MichaƂ Marczyk
ok that explains my misunderstanding, thanks. - Kevin

3 Answers

29
votes

I guess I just needed to read the docs more:

(select-keys m [:a :b])

Although I'm still not sure what the intention was with the example I found...

8
votes

If you "iterate" over a map, you'll get key-value pairs rather than keys. For instance,

   user=> (map #(str %) {:a 1, :b 2, :c 3})
   ("[:a 1]" "[:b 2]" "[:c 3]")

Thus your anonymous function tries to evaluate (some [:a "x"] [:a :b]) which clearly does not work.

The ideomatic solution is to use select-keys as mentioned in another answer.

1
votes
(filter 
  (fn [x] 
    (some #{(key x)} [:a :b])) m)

Would do the same using filter and some (but uglier and slower).

This works by filter all from m if some [:a :b] is in the set #{(key x)} (i.e. using a set as predicate) then return the map entry.