From Stateful Iteration in Python to Stateless Clojure
I have a piece of Python code that leverages a stateful object and was wondering how to reimplement it in the inherently stateless Clojure, without resorting to its facilities for managing state. It turns out to be simple and beautiful.
The core of the code is a for loop that transform a list of maps, while also using input from another iterator-like object, a Palette. The Palette has the method next() that remembers the last color used and returns the next one. Here it is:
In Clojure, we would rather have no such state (for reasons I won't discuss now). However, when writing idiomatic Clojure, we actually don't need to.
We will use the fact that map - the function that can transform collection items - can take not just one but any number of collections, iterating over all of them in parallel. Instead of using the stateful palette.next(), we will create an infinite sequence of (repeating) colors and pass it into map together with the items collection. We want also the index of the items so we pass in a third sequence, the range from 0 to infinity. The map function will be thus called with (the current index, the current item, the current color).
Using infinite sequences is quite common (and elegant) in Clojure.
This is what the code could look like:
Disclaimer: I am only learning both Python and Clojure.
The core of the code is a for loop that transform a list of maps, while also using input from another iterator-like object, a Palette. The Palette has the method next() that remembers the last color used and returns the next one. Here it is:
class Palette:
last_idx = 0
colors = ["C04000", ...]
def next(self):
self.last_idx = (self.last_idx + 1) % len(self.colors)
return self.colors[self.last_idx]
graph_items = []
for idx, item in enumerate(item_maps):
graph_items.append({
"itemid": item['itemid'],
"color": palette.next(),
"sortorder": idx
})
In Clojure, we would rather have no such state (for reasons I won't discuss now). However, when writing idiomatic Clojure, we actually don't need to.
We will use the fact that map - the function that can transform collection items - can take not just one but any number of collections, iterating over all of them in parallel. Instead of using the stateful palette.next(), we will create an infinite sequence of (repeating) colors and pass it into map together with the items collection. We want also the index of the items so we pass in a third sequence, the range from 0 to infinity. The map function will be thus called with (the current index, the current item, the current color).
Using infinite sequences is quite common (and elegant) in Clojure.
This is what the code could look like:
;; An infinite sequence of the colors (starting
;; again with the 1st one, when all exhausted)
(def palette (cycle ["C04000" "..."]))
(defn make-graph-items [items]
(map
(fn [idx item color] {"itemid" (item "itemid"), "color" color, "sortorder" idx})
(range) items palette))
(def graph-items (make-graph-items items))
Conclusion
Statelessness is not scary. It is fun.Disclaimer: I am only learning both Python and Clojure.