Reputation: 150
I'm relatively new to Clojure, so I'm having trouble wrapping my mind around how to make the following work.
Starting with a Java method (from the reflect package), I want to extract various properties and end up with a map that looks something like the following:
{ :name "test-method
:return-type "String"
:public true
}
Since the logic of building the keys can be rather complex, I'd like to chain a series of functions that take the current map and the method object and either modify the map or return it as is. Something like:
(defn build-public
[acc method]
(if (is-public? method) (assoc acc :public true) acc))
Which might be called like:
(make-method-map method [build-public build-return-type build-name])
I've tried a couple of different approaches but can't seem to make it work, any suggestions would be greatly appreciated.
Upvotes: 1
Views: 726
Reputation: 4138
I like the reduce fns
option, but here is an alternative with a bit different function signatures:
(defn make-name [method] {:name (str (:a method) "-" (:b method))})
(defn make-other [method] {:a-is-3 (= (:a method) 3)})
(defn make-method-map [method fns]
(into {} ((apply juxt fns) method)))
(defn make-method-map [method fns]
(reduce merge ((apply juxt fns) method)))
(defn make-method-map [method fns]
(apply merge ((apply juxt fns) method)))
(make-method-map {:a 1 :b 2} [make-name make-other])
; {:name "1-2", :a-is-3 false}
juxt returns a vector of function return values, here the acc
is not taken as an input argument but instead into
or reduce
is used to "merge" these hash-maps into the final result. Originally I thought of using comp
but these styles of functions aren't that "composable".
Upvotes: 0
Reputation: 1552
I would write make-method-map
something like this:
(defn method-map [method]
(-> {}
(assoc :return-type (get-return-type method))
(assoc :name (get-name method))
(cond-> (public? method) (assoc :public? true))))
Note how you can nest cond->
(or as->
or ->>
) inside a ->
. Putting any of them inside of a ->>
or similar might not work as you'd expect, though.
Why that is is left as an exercise to the reader.
Actually I would write the thing like this:
(defn method-map [method]
(-> {}
(assoc :return-type (get-return-type method))
(assoc :name (get-name method))
(assoc :public? (public? method))))
Or with a literal map if I could get away with it (one step doesn't depend on the next, etc.)
(defn method-map [method]
{:return-type (get-return-type method)
:name (get-name method)
:public? (public? method)})
But that doesn't show off the trick with nesting cond->
.
If you find yourself with a vector of functions, then leetwinski's answer is good. Like everything in programming, it depends.
Edit: For a real world example of a vector of functions being used, check out the concept of interceptors in re-frame or pedestal.
Upvotes: 0
Reputation: 17859
the simple way is to apply every function one by one with reduce:
user> (defn make-method-map [method fns]
(reduce (fn [acc f] (f acc method))
{} fns))
#'user/make-method-map-2
user> (defn make-name [acc method]
(assoc acc :name 123))
#'user/make-name
user> (defn make-other [acc method]
(assoc acc :something "ok"))
#'user/make-other
user> (make-method-map {:a 1 :b 2} [make-name make-other])
;;=> {:name 123, :something "ok"}
Upvotes: 2
Reputation: 16194
One way to do this is with cond->
which combines the concepts of cond
and ->
.
(cond-> {:my-map 1}
(= 2 2) (assoc :two-equals-two true)
(= true false) (assoc :not-possible "hey"))
=> {:my-map 1, :two-equals-two true}
The clauses on the left determine whether the forms on the right will be evaluated, with the initial value threaded through in the first position.
Upvotes: 0