I was recently playing around with D3 and ClojureScript and I ended up creating this silly exhaustive sort simulator for the TSP. I ended up learning a couple of things:
Lazy Sequences are useful even outside of a pure Clojure context.
I actually came across a good use case for lazy sequences putting this together. The traveling salesman problem is an NP-hard problem and an exhaustive solution to it is an O(n!) algorithm, which makes it ridiculously slow for anything but a trivial number of nodes. Now, the goal of my simulator was not to actually calculate solutions to the TSP, but just to show the exhaustive sort in action. You can see what it does by looking at the simulator, but in short, it shows each permutation in sequence as it searches for the best one.
The intuitive way of doing this would be to write code which computes each stage of the sort and then modifies the SVG elements accordingly. The problem with this approach is that it merges the sorting code with the visualization code, which would make it more difficult to simulate different sorts if I wanted to, or to adjust the visualizations. In addition, D3 is asynchronous which means this approach won’t work anyway as successive calls to D3 will not actually animate them in sequence. In theory what I would have had to do was create a queue which D3 consumed from and which the sorting algorithm wrote to, but this would require both unnecessary state and threading, which Javascript doesn’t really do.
The real solution was to have D3 make successive calls to the exhaustive algorithm in a callback. This could be an unwieldy thing to do, trying to pass the entry current state of the algorithm in a map between D3 and get-next-permutation
but using a lazy sequence actually made it simple. Generating a lazy sequence of permutations and passing that sequence to my D3 function means that each state will be computed when needed, but not sooner. This means that we can treat an exhaustive set of permutations for a hundred nodes 1 as if it where an ordinary sequence and not worry about the memory usage or waiting. It also means that the sorting code can be strictly divorced from the visualization code, which makes it simple to write new sorts or visualizations without having to redo other parts of the program. It also makes the code more DRY. You’ll notice that the resulting simulation runs very smoothly.
Macros in ClojureScript are annoying.
My simulator used a couple of macros which were probably unnecessary but I chose to use anyway for the exercise so to speak. ClojureScript, it turns out does not include eval
and as such does not natively support macros either.2 Macros in ClojureScript are actually Clojure macros which are included using the :require-macros
construct. This is annoying mainly because they are defined in a separate namespace from the rest of the ClojureScript code. This means that not only do you have to define the ClojureScript macros in a separate file, probably in a separate part of the application, but you also have to use hacks to prevent the code from expanding into namespaces which don’t exist in the ClojureScript environment.
For example, suppose you want to use the Math.pow()
function in your ClojureScript macro? You might attempt something like this:
(defmacro foo [a b c ...]
`(Math/pow (+ ~a ~b ~a ...)))
This looks like it will work, but because this code will be expanded in Clojure, and Math
is both a Java and Javascript object, Math/pow
will expand to the Java name, java.lang.Math.pow
rather than the Javascript function Math.pow()
, and the resulting code will result in an undefined function error. The solution is to use a symbol capture:
(defmacro foo [a b c ...]
`(~'Math/pow (+ ~a ~b ~a ...)))
but this is a hack and is frowned upon in Clojure exactly because it can lead to symbols being misinterpreted. The makers want to solve this by writing a native ClojureScript macro system(Apparently not :)) but it seems to me that just writing a new macro form which expands its arguments differently, you could do away with a lot of the annoyance.
D3 is not as hard as it’s made out to be.
For some reason D3 is often described as having a large learning curve but I haven’t found that to be so. The only real issue in my experience is oin understanding that it is rather strictly declarative and working within the resulting limitations. You don’t tell D3 what to do or how to do it but instead tell it what you want accomplished and then let it do it’s job. When you get the hang of this, it’s actually very simple.
It’s a little misleading that D3 makes such heavy use of method chaining, because typically when you chain methods, the actions they signify occur one after another, sequentially. However, D3 uses method chaining to define different aspects of a data binding/transformation/animation. Values that in other libraries or languages would be set in one huge complicated function are instead set with chained methods and the action they define isn’t launched until the function chain is run. So:
d3.selectAll("p").data(some_data)
.style(function(d) {d.style})
.text(function(d) {d.text});
tells D3 that it should change the style and text of p
elements in an HTML document but it doesn’t actually run the operations directly. Instead, it builds the operation and d3 performs it asynchronously after this code has been evaluated. So, you can’t do things like this:
d3.selectAll("p").data(some_data)
.style(function(d) {d.style})
.text(function(d) {d.text})
.data(some_more_data)
.style(function(d) {d.style})
.text(function(d) {d.text});
or like this:
d3.selectAll("p").data(some_data)
.transition()
.style(function(d) {d.style})
.text(function(d) {d.text});
d3.selectAll("p").data(some_more_data)
.transition()
.style(function(d) {d.style})
.text(function(d) {d.text});
It won’t work. You have to use callbacks for successive transitions. D3 is meant to be called when data changes to bind HTML or SVG to data and not really as a general purpose SVG transformation library.
- 100! => 93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000 ↩
- Also, this means that ClojureScript, unlike Clojure, is not a true Lisp because the entire language is not present at all times. This is an understandable but disappointing trade-off. The better solution IMHO, would have been to make dead-code elimination an optional feature, but incompatible with
eval
. Meaning that the majority of application which do not needeval
, could be made much more efficient, but those that do need it would have it available. Actually, this is a feature I would like in JVM Clojure because it would make it possible to write fast-loading Clojure programs just by avoidingeval
use. Clojure on Android might actually be feasible in that case. ↩