2
votes

I've been wrestling with this small Clojure snippet for a while, but kept getting the feeling that there is a more idiomatic, simpler approach.

Desired behaviour; convert "1" -> true, "0" -> false. Else return the argument as is:

(= (mapper {:column 0} ["monkey" "stuff"]) "monkey")
(= (mapper {:column 0} ["1" "stuff"]) true)
(= (mapper {:column 0} ["0" "stuff"]) false)

This was my first attempt; a naive imperative approach:

(defn mapper 
  [attr-map row]
  (let [x (row (:column attr-map))
        y ({"1" true "0" false} x)]
    (if (nil? y) 
      x
      y)))

Second attempt:

(defn mapper 
  [attr-map row]
  ((comp #({"1" true "0" false} % %) row :column) attr-map))

Can anyone find a better solution?

2
Why not something simple like a cond or core.match?stonemetal
Just a side comment: What's imperative about the first example? To me, "imperative" means "using side effects". (But I'm new to Clojure, so maybe I'm misunderstanding what the code does.) eg @dAni's solution isn't imperative. let and if aren't imperative, for example. "Functional" is used in one narrow sense to refer to methods that are common in functional programming. I think you're asking for "functional style" ways of doing what you want--nothing wrong with that. But the opposite of that style isn't "imperative", to my mind. (let can be viewed as an anonymous function call.)Mars

2 Answers

5
votes

If the :column key is standard, you can use destructuring.

(defn mapper [{c :column :or {c 0}}
              {item c}]
  ({"1" true "0" false} item item))

How it works:

({"1" true "0" false} item item)

Hashmaps can be treated as functions, whose first argument is a key, to retrieve a value. This form also accepts a second optional argument that is returned when the hashmap does not contain the key.

{c :column :or {c 0}}

This destructuring form uses :column as a key into the item passed into the first argument of the function. (In this case that is your {:column 0} hashmap.) The optional :or keyword allows for default values when the :column key does not exist, or the argument is not a valid collection (ex. nil, numbers, dates, etc). c will now contain either the value of the :column key, or the default value of 0.

{item c}

Since the collection passed as the second argument is indexable, you can use the same form used previously. The var named in the previous destructuring form, c, contains the value of :column and it can be used to index into the second argument, and assign the resulting value to item.


Code Golf:

If you can guarantee the input argument format, it can be further reduced to:

(fn [{i :column} {x i}] ({"1" true "0" false} x x))

3
votes

I would go for a more longer (and more readable?) implementation:

(defn mapper [{c :column} row]
    (let [v (row c)]
      (condp = v
             "1" true
             "0" false
             v)))