Functionally Competent
Here’s a piece of somewhat–ugly, single–purpose Python code, what it does is not particularly interesting:
def span(data):
r = []
for row in data:
if r and row[1] == r[-1][2]:
r[-1][1] = row[0]
else:
r.append([row[0], row[0], row[1]])
return r
After a couple of days of wondering how this might be done in Clojure, I stumbled upon the idea of a partition, and went looking in the docs. Here’s the Clojure version:
(defn span [data]
(map (fn [spn] [(first (first spn))
(first (last spn))
(second (first spn))])
(partition-by (fn [row] (second row)) data)))
To my eye, the Clojure version is a lot nicer (although it could be novelty, or the square brackets containing “magic numbers” in the Python version).
The beauty of the Clojure version comes from the partition-by function, replicated below:
(defn partition-by
[f coll]
(lazy-seq
(when-let [s (seq coll)]
(let [fst (first s)
fv (f fst)
run (cons fst (take-while #(= fv (f %)) (next s)))]
(cons run (partition-by f (seq (drop (count run) s))))))))
After a few minutes of working out what it does, it “popped”, and at the same time I had a break–through in my understanding of functional programming.
For completeness, here’s a version of partition-by in Python:
import itertools
def partition_by(f, coll):
if not coll:
return []
head, tail = coll[0], coll[1:]
run = [head] + list(itertools.takewhile(lambda t: f(t) == f(head), tail))
return [run] + func_partition_by(f, coll[len(run):])
Which can be used to refactor the original span function to:
def span(data):
return map(
lambda r: [r[0][0], r[-1][0], r[0][1]],
partition_by(lambda row: row[1], data))
The nice thing about the original Clojure version, and the final Python version is that the format of the “rows”, and the logic for partitioning data are separate: something I wouldn’t have done if it weren’t for my recent interest in Clojure.