Convert list of vars to a name-var map - clojure

I'm using Yesql library for Clojure. Its defqueries macro returns a list of Vars representing SQL queries. I want to convert that list into a map of query names to Vars, and have come up with a variant below:
(def main-queries (defqueries "sql/main.sql"))
(def query-map
(apply hash-map
(reduce
(fn [coll query]
(conj coll (->> query meta :name keyword) query))
[] main-queries)))
It seems very inelegant and cumbersome for such an easy task. Can you suggest a shorter and more idiomatic version?
Thanks!

You can create the name -> var mappings and then use into:
(def query-map
(let [pairs (map (fn [v] [(->> v meta :name keyword) v]) main-queries)]
(into {} pairs)))

Related

clojure: filtering a vector of maps by keys existence and values

I have a vector of maps like this one
(def map1
[{:name "name1"
:field "xxx"}
{:name "name2"
:requires {"element1" 1}}
{:name "name3"
:consumes {"element2" 1 "element3" 4}}])
I'm trying to define a functions that takes in a map like {"element1" 1 "element3" 6} (ie: with n fields, or {}) and fiters the maps in map1, returning only the ones that either have no requires and consumes, or have a lower number associated to them than the one associated with that key in the provided map (if the provided map doesn't have any key like that, it's not returned)
but I'm failing to grasp how to approach the maps recursive loop and filtering
(defn getV [node nodes]
(defn filterType [type nodes]
(filter (fn [x] (if (contains? x type)
false ; filter for key values here
true)) nodes))
(filterType :requires (filterType :consumes nodes)))
There's two ways to look at problems like this: from the outside in or from the inside out. Naming things carefully can really help when working with nested structures. For example, calling a vector of maps map1 may be adding to the confusion.
Starting from the outside, you need a predicate function for filtering the list. This function will take a map as a parameter and will be used by a filter function.
(defn comparisons [m]
...)
(filter comparisons map1)
I'm not sure I understand the comparisons precisely, but there seems to be at least two flavors. The first is looking for maps that do not have :requires or :consumes keys.
(defn no-requires-or-consumes [m]
...)
(defn all-keys-higher-than-values [m]
...)
(defn comparisons [m]
(some #(% m) [no-requires-or-consumes all-keys-higher-than-values]))
Then it's a matter of defining the individual comparison functions
(defn no-requires-or-consumes [m]
(and (not (:requires m)) (not (:consumes m))))
The second is more complicated. It operates on one or two inner maps but the behaviour is the same in both cases so the real implementation can be pushed down another level.
(defn all-keys-higher-than-values [m]
(every? keys-higher-than-values [(:requires m) (:consumes m)]))
The crux of the comparison is looking at the number in the key part of the map vs the value. Pushing the details down a level gives:
(defn keys-higher-than-values [m]
(every? #(>= (number-from-key %) (get m %)) (keys m)))
Note: I chose >= here so that the second entry in the sample data will pass.
That leaves only pulling the number of of key string. how to do that can be found at In Clojure how can I convert a String to a number?
(defn number-from-key [s]
(read-string (re-find #"\d+" s)))
Stringing all these together and running against the example data returns the first and second entries.
Putting everything together:
(defn no-requires-or-consumes [m]
(and (not (:requires m)) (not (:consumes m))))
(defn number-from-key [s]
(read-string (re-find #"\d+" s)))
(defn all-keys-higher-than-values [m]
(every? keys-higher-than-values [(:requires m) (:consumes m)]))
(defn keys-higher-than-values [m]
(every? #(>= (number-from-key %) (get m %)) (keys m)))
(defn comparisons [m]
(some #(% m) [no-requires-or-consumes all-keys-higher-than-values]))
(filter comparisons map1)

clojure when-let alternative for an empty array?

I'm generating json as literally as I can in clojure. My problem is that certain branches of the json are only present if given parameters are given. Here is a sample of such a condition
(defn message-for
[name uuid & [generated-uuids]]
{:message {:id (generate-uuid)
:details {:name name}
:metadata {:batch (merge {:id uuid}
(when generated-uuids (let [batches (map #(array-map :id %) generated-uuids)]
{:generatedBatches batches})))}}})
Unfortunately the when/let part is quite ugly. This same could be achieved using when-let as following but it doesn't work because my map returns [] instead of a nil.
(defn message-for
[name uuid & [generated-uuids]]
{:message {:id (generate-uuid)
:details {:name name}
:metadata {:batch (merge {:id uuid}
(when-let [batches (map #(array-map :id %) generated-uuids)]
{:generatedBatches batches}))}}})
Any ideas if I could somehow make when-let consider an empty list/array/seq as false so I could clean up my code a bit?
not-empty returns its argument if it is not empty.
When using when-let with a collection, always use not-empty
to retain the collection type
make refactoring easier
expressivenes
(when-let [batches (not-empty (map ...))]
...)
In your case I'd however prefer something like this:
...
:metadata {:batch (cond-> {:id uuid}
(seq generated-uuids)
(assoc :generatedBatches (map ...)))}
...
Notice that all three of the advantages listed above where met, without a nested let.
Also notice a new advantage
easier to extend with more conditions lateron
seq returns nil on an empty input sequence so you could do:
(when-let [batches (seq (map #(array-map :id %) generated-uuids))]
{:generatedBatches batches}))}}})

Idiomatic way of finding functions in a namesspace containing specific metadata?

I'm trying to figure out the best way to troll a namespace for functions that contain a specific bit of metadata. I've come up with a solution, but it feels a little awkward and I'm not at all sure I'm going about it the right way. There's a second component to this as well: I don't just want the names of the functions, I want to find them and then execute them. Here's a snippet of what I'm doing presently:
(defn wrap-routes
[req from-ns]
(let [publics (ns-publics from-ns)
routes (->>
(keys publics)
(map #(meta (% publics)))
(filter #(= (:route-handler %) true))
(map #(:name %)))
resp (first
(->>
(map #((% publics) req) routes)
(filter #(:status %))))]
(or resp not-found)))
As you can see, I'm doing all sorts of gymnastics to see if my metadata is attached to any functions in a given namespace and then am doing extra work after that to get the actual function back. I'm sure there must be a better way. So my question is, how would you do this?
(defn wrap-routes [req from-ns]
(or (first (filter :status
(for [[name f] (ns-publics from-ns)
:when (:route-handler (meta f))]
(f req))))
not-found))
You can do something like this:
(defn wrap-routes
[req from-ns]
(->> (ns-publics from-ns)
(filter #(:route-handler (meta (%1 1))))
(map #((%1 1) req))
(filter #(:status %))
first
(#(or % not-found))))

Alternatives for converting list of nested maps to a map

I have the following working code to convert a list with nested maps (actually tweet data) to a map:
(defn filter
"This function returns a map with the user as key, #followers as value"
[raw-tweets]
(let [users (map :user raw-tweets)
names (map :name users)
followers (map :followers_count users)]
(zipmap names followers)))
Although this works as expected, I was wondering if there would be a more idiomatic way to do this in Clojure. Any alternatives?
What you have is fine, though you can build the map as you go by using reduce:
(defn user-followers [raw-tweets]
(reduce #(assoc %1 (:name %2) (:followers_count %2))
{} (map :user raw-tweets)))
I'm only starting to learn clojure but I think this way might be a bit more idiomatic. It's an alternative in any case.
(defn filter
"This function returns a map with the user as key, #followers as value"
[raw-tweets]
(into {} (map #(let [user (:user %)]
[(:name user) (:followers_count user)])
raw-tweets)))
It maps over the raw tweets with a function that retrieves the user for each tweet and returns a vector with the name and followers count for that user. The into function takes two sequences and conjoins every element of the second one onto the first, which will turn the list of vectors into a map before it's returned from the filter function.
I find #Daan's answer nice, but I'd add destructuring into the mix.
(defn filter-tweets
"This function returns a map with the user as key, #followers as value"
[raw-tweets]
(into {} (map (fn [{{name :name follower-count :followers_count} :user}]
[name follower-count])
raw-tweets)))
I don't like the (map (fn ...)) pattern - it's really just an ugly way to write a for comprehension. I'd write this as:
(into {}
(for [{:keys [user]} raw-tweets]
((juxt :name :followers_count) user)))
Or this, which feels a little less natural to me but avoids inventing names for values you're just going to use once anyway.
(into {} (map (comp (juxt :name :followers_count) :user)
raw-tweets))

clojure convert lazy-seq to hash map

I'm a clojure beginner. How do I create a map from a lazySeq?
(def fields [:name :age :color])
(def values ["joe" 32 "red"])
(def record (interleave fields values))
(def mymap (into {} record)) ;; ????
;; (get mymap :age)
;; 32
To answer your actual question:
(apply hash-map record)
But since you actually already have separate keys and values, I would suggest skipping the interleave step and instead writing
(zipmap fields values)
Or if you have your heart set on into, you could
(into {} (map vector fields values))
This isn't sensible at all, but since the original question wanted to use into with record:
(into {} (map vec (partition 2 record)))