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
.
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)))
...)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With