toofarsideways toofarsideways - 1 month ago 5x
JSON Question

Working with mutation in Clojurescript

I'm learning how Clojurescript works by trying to draw some grids from a JSON structure using d3.js. I'm using strokes to access d3.

The JSON looks like this:


It represents a 4 by 4 grid. I'm trying to add values to the cells such as height, width, x and y coordinates so that then it's just a simple case of passing the data to d3 to be drawn.

For example it would look something like this:


Usually I'd map over the structure with a transformation function to convert the cells from their current value into a new form, however this approach just doesn't appear to work. I've tried
map-indexed: (map-indexed #(doto %2 (aset "width" %1)) row)
but this doesn't appear to correctly transform the values. It is quite likely that I am accessing or setting the values incorrectly.

The current iteration of the code looks like this:

(defn board->grid [grid-width grid-height board square]
(let [x-length (count board)
y-length (count (first board))
same (min (/ grid-width x-length) (/ grid-height y-length))
grid-item-width (if square same (/ grid-width x-length))
grid-item-height (if square same (/ grid-height y-length))
start-x (/ grid-item-width 2)
start-y (/ grid-item-height 2)
values (array)
grid (array)
data (js->clj board :keywordize-keys true)]
(doseq [x (range x-length)
y (range y-length)]
(let [current-cell (aget data y x)]
(.log js/console (apply str (aset (aget data y x) "a" "b")))
(.push grid (aget data y x))))
(.text ($ :#status) grid)))

Any help would be appreciated! Or better yet, suggestions of better approaches, I can't help but feel I'm going about this slightly wrong!


The basic rule of thumb for working with mutability in clojure and clojurescript is "don't". JS arrays and objects don't implement most of the protocols that functions rely on to do their work. For example, plain js arrays are not seqable! Do most of your work with immutable data structures and convert to mutable equivalents only when you need to interface with other libraries.

There are some additional functions which are array-specific: into-array, to-array, aget, aset, amap, areduce and alength. (See this cheat sheet.)

There are also many closure library functions you may find helpful in goog.array, goog.object, or goog.structs if you want to stick with mutable data structures. (Remember, clojurescript includes the google closure library!)

You can also use this form everywhere:

(defn amap2d [arr f]
  (doseq [x (range (alength arr))
          y (range (alength (aget A x)))
         :let [cell (aget A y x)]]
    (f x y cell)))

But I think you'll be happier doing js->clj, working on the data, and then clj->js right before you pass it to d3.