开发者

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

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 in开发者_运维知识库stead of just an if.


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})))


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)})


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)]))))


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)))
...)


Your first code example seems most readable to me. Readability is often preferable over efficiency, unless you have a performance critical part of your code. But here's a way to only evaluate your conditionals once. I highly doubt that its performance makes much difference with your code. In terms of elegance I still prefer your first example, since it is clearer to see directly what the key-value pairs are.

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


By shamelessly stealing others ideas.

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

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜