3
\$\begingroup\$

I was helping someone on SO with an assignment to create a macro that accepts an SQL-like syntax, and optionally sorts and filters a collection. I realized that while their instructor is insisting on them using macros, macros aren't actually required at all. I decided to try my hand at creating a function that solves the assignment (roughly).

This is my first stab at it. It's not great yet (there's no way to pick between ascending and descending sort order for example), but it's a first step.

An example of its usage:

(def test-db [{:id 1, :name "A"},
              {:id 0, :name "B"},
              {:id 3, :name "C"},
              {:id 2, :name "D"},
              {:id 4, :name "E"}])

(select [:name] :from test-db :where [:id < 3] :order-by :id)
=> ("B" "A" "D")

Considering this only took me like 20 minutes to write, I'm pretty happy with it. I do have a few major concerns though:

  • I dislike how I'm turning the variadic parameters into a map. Right now, I'm using

    (let [arg-map (into {} (map vec (partition 2 args))) ...
    

    But this seems overly complicated. It doesn't seem like lazy-seqs can be treated as MapEntries like vectors can, so (map vec ... is necessary to allow the conversion. A better way here would be appreciated.

  • Originally I had :from as part of args, but it's a necessary field, so it seemed messy to allow it to be optional, only to check for it's existence in the function. I made it a required argument, but that made it so the second argument to select is entirely useless and discarded, which also seems messy.

  • Any other suggestions would be appreciated.


(defn- where
  "Turns a vector of [key comparator compare-value] into a predicate that filter can use."
  [arg-vec]
  (let [[k f comp-arg] arg-vec]
    (fn [elem] (f (k elem) comp-arg))))

(defn select
  "Selects the given keys from the \"database\", filtered by the :where clause, and ordered by the :order-by key.

  Example:
  (select [:id] :from [{:id 1} {:id 2}] :where [:id > 0] :order-by :id)"
  [selection _ from & args]
  (let [arg-map (into {} (map vec (partition 2 args)))
        {where-vec :where, order-key :order-by} arg-map

         filt-ordered
         (cond->> from
           where-vec (filter (where where-vec))
           order-key (sort-by order-key))]

      (mapcat #(map % filt-ordered) selection)))
\$\endgroup\$
  • \$\begingroup\$ Instead of (map vec ...) you can (map (fn [[k v]] [k v])). \$\endgroup\$ – Chris Murphy Dec 9 '17 at 20:23
  • \$\begingroup\$ @ChrisMurphy As in (map (fn [[k v]] [k v]) (partition 2 ...)? \$\endgroup\$ – Carcigenicate Dec 9 '17 at 20:41
  • \$\begingroup\$ To the result of those 2 steps you are still going to have to (into {} ...), AFAIK. So (->> x (partition 2) (map (fn [[k v]] [k v]) (into {}))). \$\endgroup\$ – Chris Murphy Dec 10 '17 at 0:04

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.