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."
  (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.

  (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

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

      (mapcat #(map % filt-ordered) selection)))
  • \$\begingroup\$ Instead of (map vec ...) you can (map (fn [[k v]] [k v])). \$\endgroup\$ Dec 9, 2017 at 20:23
  • \$\begingroup\$ @ChrisMurphy As in (map (fn [[k v]] [k v]) (partition 2 ...)? \$\endgroup\$ Dec 9, 2017 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\$ Dec 10, 2017 at 0:04


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Browse other questions tagged or ask your own question.