I’ve survived another Clojure recurs or not recurs crisis. I understand why Clojure has sequences, but recursion is built into the language, and why its use represents a non-functional programming style is news to me. I have albeit helpful comments to that effect.
A lot of list like things can be abstracted into sequences, without having to know what kind of sequence, so I am beginning to get why someone would want to use map and other sequence-oriented functions.
However, sometimes, you may want to take the difference between two similar spreadsheet reports. A value in one column (a key) of Report A should be found in Report B in another column.
I spent a lot of extra learning time trying to take a list of keys present in Report A and see if they were also present in Report B using only Clojure’s sequence functions. Personally, I wanted to write a function to perform a match, return nil, if the match was found, or the search row if the match was not found. How hard is that?
I was frustrated, because it felt like all my logic was being re-routed, rather than part of it, like learning how to use map, filter, and so on.
The data looks like this:
[["0123456789" "SMITHFIELD" "HAM"]["1123456789" "LITTLE" "CHICKEN"] ...]
Here is the solution, which uses loop .. recur.
(defn ret-non-match-rows "Expects a sequence of sequences, like what is returned from clojure-csv. Returns nil if there's a match; else returns failing row." [s-o-s cmp-col-idx inq-row-idx inq-row] (let [inq-row inq-row] (loop [[row & remain-seq] s-o-s pos 0] (let [cmp-val (nth inq-row inq-row-idx nil)] (cond (not row) inq-row (= cmp-val (nth row cmp-col-idx)) nil :not-found (recur remain-seq (inc pos))))))) (defn test-key-exclusion "This function takes csv-data1 (the includees) and tests to see if each includee is in csv-data2 (the includeds). This function uses full rows, so ssn, lnam, and fnam can be reported." [csv-data1 pkey-idx1 csv-data2 pkey-idx2 lnam-idx fnam-idx] ; This is a case for using the thrush ->> operator in Clojure. ; Insert each result into the last position of the next call. (->> (map #(ret-non-match-rows csv-data2 pkey-idx2 pkey-idx1 %1) csv-data1) (filter (complement nil?)) (map (fn [row] (vector (nth row pkey-idx1 nil) (nth row lnam-idx nil) (nth row fnam-idx nil))))))
Used with a map statement, a sequence is returned that contains a lot of nils (hopefully) and a few non-nill entries. It is those entries that go into a missing report.
So, the moral of the story is, sometimes you want to traverse a sequence and stop when a condition, like a key match, has occurred. But do not give up on sequences.