1
votes

I've been learning Clojure for a few weeks now. I know the basics of the data structures and some functions. (I'm reading the Clojure Programming book).

I'm stuck with the following. I'm writing a function which will lower case the keys of the supplied map.

(defn lower-case-map [m]
  (def lm {})
  (doseq [k (keys m)]
    (assoc lm (str/lower-case k) (m k))))

This does what I want, but how do I return the map? Is the def correct?

I know this works

(defn lower-case-map [m]
  (assoc {} :a 1))

But the doseq above seems to be creating a problem.

1
You should never use def inside of a function on Clojure. def should only be used on the top level. Use let to define local variables instead. - Leonid Beschastny

1 Answers

5
votes

Within a function body you should define your local variables with let, yet this code looks alot like you try to bend it into an imperative mindset (def tempvar = new Map; foreach k,v in m do tempvar[k.toLower] = v; return tempvar). Also note, that the docs of doseq explicitly state, that it returns nil.

The functional approach would be a map or reduce over the input returning the result directly. E.g. a simple approach to map (iterating the sequence of elements, destructure the key/value tuple, emit a modified tuple, turn them back into a map):

user=> (into {} (map (fn [[k v]] [(.toLowerCase k) v]) {"A" 1 "B" 2}))
{"a" 1, "b" 2}

For your use-case (modify all keys in a map) is already a nice core function: reduce-kv:

user=> (doc reduce-kv)
-------------------------
clojure.core/reduce-kv
([f init coll])
  Reduces an associative collection. f should be a function of 3
  arguments. Returns the result of applying f to init, the first key
  and the first value in coll, then applying f to that result and the
  2nd key and value, etc. If coll contains no entries, returns init
  and f is not called. Note that reduce-kv is supported on vectors,
  where the keys will be the ordinals.

user=> (reduce-kv (fn [m k v] (assoc m (.toLowerCase k) v)) {} {"A" 1 "B" 2})
{"a" 1, "b" 2}