Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I return a clojure map with fixed keys and conditional values?

Tags:

idioms

clojure

I have a function that returns a map. The keys are static, but the values are conditional. Like this:

(defn map-returning-function [x y]
  {:a (if (some-test-fn x)  "one value" "other value"
   :b (if (some-test-fn x)   15         25
   :c (if (different-test y) :one       :two})

Is there some more elegant way to achieve this without needing to write the if test for each value? The only other way I can think of is

(defn another-map-returning-function [x y]
  (if (some-test-fn x)
    {:a "one value",  :b 15, :c (if (different-test y) :one :two)}
    {:a "other value" :b 25, :c (if (different-test y) :one :two)}))

which doesn't seem much better to me since it repeats the key names for each branch of the conditional and it repeats of the function call on different-test. And heaven forbid that I need a cond instead of just an if.

like image 883
stand Avatar asked Jun 08 '11 05:06

stand


4 Answers

Another variation one could think of is to use merge. One case serves as default, which is modified depending on the function arguments. This way you can easily group the tests and spice things up with some comments if necessary.

(defn map-returning-function
  [x y]
  (merge {:a "other value"
          :b 25
          :c :two}
         (when (some-test-fn x)
           {:a "one value"
            :b 15})
         (when (different-test y)
           {:c :one})))

Alternatively the other way around, depending on what you consider to be the default.

(defn map-returning-function
  [x y]
  (merge {:a "one value"
          :b 15
          :c :one}
         (when-not (some-test-fn x)
           {:a "other value"
            :b 25})
         (when-not (different-test y)
           {:c :two})))
like image 68
kotarak Avatar answered Sep 22 '22 09:09

kotarak


How about this:

(let [test-x (some-test x) test-y (some-test y)] 
  (conj 
    (if test-x {:a "one value" :b 15} {:a "other value" :b 25}) 
    (if test-y {:c :one} {:c :two})))

Conditions executed once and in one place, then used in another. Depends on context and personal preference though. Something more like your example may be cleaner:

(let [test-x (some-test x) test-y (some-test y)] 
  {:a (if test-x "one value" "other value")
   :b (if test-x 15 25)
   :c (if test-y :one :two)})
like image 28
Konrad Garus Avatar answered Sep 21 '22 09:09

Konrad Garus


Looking at what you're asking i'd say one way to do this would be constructing a function that looks something like this:

(pred-map 
  {:a [some-test-fn "one-value" "other-value"] 
   :b [some-test-fn 15 25] 
   :c [different-test :one :two]} 
  x 
  y)

where x is the argument to all references of first menti0oned function and y to the second one

a way to achieve this could be following:

(defn get-value [p tv fv a]
  (if (p a) tv fv))

(defn get-predicate-set [m]
  (set (map first (vals m))))

(defn get-arg-map [m args]
  (zipmap (get-predicate-set m) args))

(defn get-arg [p m args]
   ((get-arg-map m args) p)) 

(defn get-key-value-pair-creator [m args]
  (fn [[k [p tv fv]]]
    [k
     (get-value 
       p
       tv
       fv 
       (get-arg p m args))]))


(defn pred-map [m & args]
  (into {}
    (map 
      (get-key-value-pair-creator m args)
      m)))

These functions however rely on arguments being mapped to functions by equality (which seems to go with references) so it will not understand two equal anonymous functions as the same one.

if you don't mind repeating the arguments you create a simpler function looking like this:

(pred-map
  {:a [(some-test-fn x) "one value" "other-value"]
   :b [(some-test-fn x) 15 25]
   :c [(different-test y) :one :two]})

by following simple function:

(defn pred-map [m] (into {} (for [[k [p tv fv]] m] [k (if p tv fv)])))

or in pointfree style:

(def pred-map (comp (partial into {}) (partial map (fn [[k [p tv fv]]] [k (if p tv fv)]))))
like image 30
Verneri Åberg Avatar answered Sep 21 '22 09:09

Verneri Åberg


Yet another way of doing it :-)

(defmulti map-returning-function 
    (fn [x y] [ (some-test-fn x) (different-test y) ]))

(let [x-values {true  {:a "one value" :b 15}
                false {:a "other value" :b 25}}
      y-values {true {:c :one} false {:c :two}}]

  (defmethod map-returning-function [false false] [x y]
     (merge (x-values false) (y-values false)))

  (defmethod map-returning-function [true true] [x y]
     (merge (x-values true) (y-values true)))
...)
like image 26
hsanjeewa Avatar answered Sep 23 '22 09:09

hsanjeewa