I have three similar functions, they filter a collection of maps matching a key (column) against its value, case-insensitively.
Here is the original code I want to DRY up:
;; gets a collection filtered by a column, exact match
(defn get-collection-by-equals [collection-name column-name column-value]
(filter #(= (string/lower-case (column-name %)) (string/lower-case column-value)) (cached-get-collection collection-name)))
;; gets a collection filtered by a column, with partial match, case-insensitive
(defn get-collection-by-like [collection-name column-name column-value]
(filter #(string/includes? (string/lower-case (column-name %)) (string/lower-case column-value)) (cached-get-collection collection-name)))
;; gets a collection filtered by a column, which starts with given value, case-insensitive
(defn get-collection-by-starts-with [collection-name column-name column-value]
(filter #(string/starts-with? (string/lower-case (column-name %)) (string/lower-case column-value)) (cached-get-collection collection-name)))
You can see how similar the code is, I'm just using a different matching strategy in each case, =
, includes?
and starts-with?
.
My first attempt was as follows:
;; returns a function which does a case-insensitive match between given column and value for the given map
(defn matching-fn [match-fn column-name column-value]
(fn [map] (match-fn (string/lower-case (column-name map)) (string/lower-case column-value))))
;; gets a collection filtered by a column, exact match
(defn get-collection-by-equals [collection-name column-name column-value]
(filter #((matching-fn = column-name column-value) %) (cached-get-collection collection-name)))
;; gets a collection filtered by a column, with partial match, case-insensitive
(defn get-collection-by-like [collection-name column-name column-value]
(filter #((matching-fn string/includes? column-name column-value) %) (cached-get-collection collection-name)))
;; gets a collection filtered by a column, which starts with given value, case-insensitive
(defn get-collection-by-starts-with [collection-name column-name column-value]
(filter #((matching-fn string/starts-with? column-name column-value) %) (cached-get-collection collection-name)))
I didn't like the readability of this solution and it occurred to me that I could just pass the matching function, instead of having a function which returns a function, I came up with this:
;; gets a collection filtered by a column, using the given function, case-insensitive
(defn get-collection-by-filter [collection-name filter-fn column-name column-value]
(filter #(filter-fn (string/lower-case (column-name %)) (string/lower-case column-value)) (cached-get-collection collection-name)))
;; gets a collection filtered by a column, exact match, case-insensitive
(defn get-collection-by-equals [collection-name column-name column-value]
(get-collection-by collection-name = column-name column-value))
;; gets a collection filtered by a column, with partial match, case-insensitive
(defn get-collection-by-like [collection-name column-name column-value]
(get-collection-by collection-name string/includes? column-name column-value))
;; gets a collection filtered by a column, which starts with given value, case-insensitive
(defn get-collection-by-starts-with [collection-name column-name column-value]
(get-collection-by collection-name string/starts-with? column-name column-value))
Is this idiomatic Clojure, are there other (better) solutions?
Using a macro seems overkill.
#((matching-fn string/includes? column-name column-value) %)
? you could just leave(matching-fn string/includes? column-name column-value)
making it more readable – leetwinskimatching-fn
returns a one arity function suitable forfilter
anyway, no need to wrap it in another anonymous function. – Kris