code-reviews

2020-07-23T20:52:40.179Z

Code puzzle and a review rolled into one. I need a way to translate essential the get-in list args into a datomic where clause.

[] -> [[]]
[:a] -> [[?r0 :a ?r1]]
[:a :b] -> [[?r0 :a ?r1] [?r1 :b ?r2]]

fn: expression, in-binding, find-binding -> where-clause

where expression is like the get-in arg [:a :b ... ]
Additionally the first and last bindings (r?0 and ?r2) are passed into the function. This is my first attempt that works:
(defn expression->where-clause
  [attrs in-binding find-binding]
  (let [->b #(symbol (str "?" %))
        f_attrs (first attrs)
        l_attrs (last attrs)]
    (cond
      (= 0 (count attrs)) []
      (= 1 (count attrs)) (into [find-binding] (conj attrs in-binding))
      (= 2 (count attrs)) [[find-binding f_attrs (->b (name f_attrs))]
                           [(->b (name f_attrs)) l_attrs in-binding]]
      :else
      (let [b    (butlast (rest attrs))
            body (:coll (reduce
                         (fn [{:keys [coll c]} n]
                           {:coll (conj coll (->b c) n (->b (inc c)))
                            :c    (inc c)})
                         {:coll []
                          :c    0}
                         b))
            fb   (first body)
            lb   (last body)]
        (partition 3
                   (into [find-binding f_attrs fb]
                         (conj body lb l_attrs in-binding)))))))
I feel like interleave should be at play here but when i wasn't sure it fit so i started with reduce to get something more basic.

jaihindhreddy 2020-07-24T22:25:17.188900Z

As you said, interleave works too, but it looks a bit more awkward to my eyes:

(defn expr->where-clause [attrs from to]
  (let [syms (->> #(symbol (str "?" (gensym)))
                  (repeatedly (dec (count attrs))))]
    (->> (conj (vec syms) to)
         (interleave attrs)
         (cons from)
         (partition 3 2))))

2020-07-24T23:34:59.189200Z

@jaihindhreddy nice. That's fairly clean. I tried and failed to get parition and interleave to get this to work. i think i was relying on the padding and not cons the from.

2020-07-24T23:36:13.189400Z

@smith.adriane thanks for the review. i cleaned it up based on this then went another direction using reduce then ponged back to what jaihindhreedy has there. 2 hours of fiddling. 🙂

1🙂
phronmophobic 2020-07-23T21:18:02.179100Z

just some initial thoughts: • 0 and 1 numbers of attributes don't seem like special cases. making them special cases in the code just makes it more verbose. the only reason to do that is if performance is especially important • why f_attrs instead of f-attrs? • b is only used once and its name is confusing. i would inline it into the reduce call • you use c to represent a number and the n to represent a keyword. if you're going to use single character names, use i ,`num` or n for the number (not c). use k to for the keyword, not n. • I think coll should be a vector of subqueries. ie.

(conj coll [ (->b c) n (->b (inc c))])
this would get rid of the partition at the end, but more importantly, it shows the structure of your intent more clearly • I would try to find a better name than coll. maybe query, stmts, or exprs? • if fb and lb are only used once, then I would inline them. giving them short names only adds unnecessary indirection • personally, I would prefer reading first-body to fb. even better, first-binding and last-binding

phronmophobic 2020-07-23T21:28:47.179500Z

in general, I would try to structure the code to reveal the high level algorithm (which is sort of subjective). for me, the high level algorithm psuedo code is like:

(defn expression->where-clause
  [attrs in-binding find-binding]

  (let [->b #(symbol (str "?" %))
         ;; make queries using (drop (butlast attrs))
        middle-queries (stuff (drop (butlast attrs)) ...)

        first-query [find-binding (first attrs) (->b 0)]
        last-query [(->b (dec (count attrs))) (last attrs) in-binding]
        ]
    (vec
     (concat
      [first-query]
      middle-queries
      [last-query])))
  )

phronmophobic 2020-07-23T21:36:00.180Z

if you are going to have special cases based on the number of attrs, I would prefer a case to a cond:

(case (count attrs)
  0 []
  1 (into [find-binding] (conj attrs in-binding))
  2 [[find-binding f_attrs (->b (name f_attrs))]
     [(->b (name f_attrs)) l_attrs in-binding]]
  
  ;; else
  (let [] ...))