I'm writing a Clojure library to generate Voronoi diagrams using the quad-edge data structure as described in this research paper. I was using refs to represent the edge records, but I wanted to change the code to be pure for a couple reasons:
- I don't want users of the library to have to worry about details like wrapping their code in a
dosync. - I want to be able to make non-destructive updates, such as clipping a diagram to a viewport without altering the original triangulation.
- Refs are not implemented in Clojurescript.
The problem is that the pure code is extremely repetitive due to passing around the data structure to every single function. Here's an example:
(defn- bubble-up [qeds cross]
(loop [qeds qeds
cross cross]
(let [l-edge (first-hit qeds cross (q/r-prev qeds cross) q/o-next)
r-edge (first-hit qeds cross (q/o-prev qeds cross) q/o-prev)
l-valid (above? qeds l-edge cross)
r-valid (above? qeds r-edge cross)]
(when (or l-valid r-valid)
(let [[qeds new-cross]
(if (or (not l-valid)
(and r-valid
(in-circle? (dest qeds l-edge) (org qeds l-edge) (org qeds r-edge)
(dest qeds r-edge))))
(connect qeds r-edge (q/sym cross))
(connect qeds (q/sym cross) (q/sym l-edge)))]
(recur qeds new-cross))))))
I've tried various other approaches, including:
- Writing the mutable part in Java.
- Using a state monad (which is slightly better but still clunky and not idiomatic.)
- Putting the data structure in a dynamic var.
Is there a standard, idiomatic way to deal with a situation like this in Clojure(script)?
Can't judge for all, but I would leave things as they are. And at the risk of stating the obvious, if some particular set of forms (that's not just a single function call) can be seen in more than 2 places, it probably deserves to be extracted into its own function. But the presented code doesn't have something like that.
If you dislike that repetition to such an extent that something must be done, as an alternative you can create a function that's like
partialbut upside-down:And then use it as:
Whether it's worth it or not is up to you. I probably wouldn't do it, unless there were many more similar lines with repeated usages of the same functions.
As a yet another alternative, you could use a dynamic variable to store the graph. But in some regards it might be even worse than a refs/atoms-based solution.