How to create an array with initial values?

#1

I’m following a react tic-tac-toe tutorial and have gotten to the point where it wants to create an array of 9 empty slots to hold the board’s state. The javascript it uses is,

this.state = {
      squares: Array(9).fill(null),
    };

What’s the clojurescript equivalent? Would you use an array? Or would a map be better? Either way, how do you create the initial data?

I don’t want to just copy the javascript, I’d rather learn how it would be done if implemented in clojurescript from the ground up.

1 Like
#2

Whether you choose a vector or a map is going to depend on how the data structure is used most naturally. Both maps and vectors are associative – the former on their keys, the latter on their indices. I suspect that the tic-tac-toe program is going to do a lot of position calculations on the assumption that you have a contiguous vector so mapping the JS example to cljs is probably going to be easier if you use a vector.

Probably the easiest way to produce a vector of nine null values is something like:

(into [] (repeat 9 nil))

or:

(vec (repeat 9 nil))
2 Likes
#3

Dang it, I swear I tried that. Turns out, I did
(vector (repeat 9 nil))
which creates
[(nil nil nil nil nil nil nil nil nil)]

Using (vec (repeat 9 nil)) does the right thing,
[nil nil nil nil nil nil nil nil nil]

Is there any sort of performance reason not to use the into version? I think I’d rather use that one for the clarity of what it’s doing.

#4
(! 527)-> clj -A:bench
Clojure 1.10.0
user=> (require '[criterium.core :refer [quick-bench]])
nil
user=> (quick-bench (vec (repeat 9 nil)))
Evaluation count : 3730704 in 6 samples of 621784 calls.
             Execution time mean : 162.266919 ns
...
nil
user=> (quick-bench (into [] (repeat 9 nil)))
Evaluation count : 1928736 in 6 samples of 321456 calls.
             Execution time mean : 315.205169 ns
...
nil
user=>

I’d probably still use the into [] version unless my code was so performance sensitive that this was a bottleneck.

3 Likes
#5

You could have done:

(apply vector (repeat 9 nil))

And if you wanted, but that’s a bit advanced, you can do:

`[[email protected](repeat 9 nil)]
3 Likes
#6

Could you explain what is happening with the second one:

`[[email protected](repeat 9 nil)]
1 Like
#7

It creates the expression [nil nil nil nil nil nil nil nil nil] compile time. ` is called syntax quote, and used to generate code dynamically. [email protected] is called splicing unquote, which lets you put multiple values inside something.

A reason to use @didibus’ code over the other suggestions, is that the macro expansion to [nil nil ...] will happen once, and you can thus save time.

If you want to learn quoting and unquoting, the guide below is a good resource. But it’s okay to focus on the runtime programming and ignore compile time programming in the beginning.

If I was doing this, though, I’d probably just inline the board like this to make it clear that it’s 3x3, though that’s not your question:

(def starting-board [nil nil nil
                     nil nil nil
                     nil nil nil])
2 Likes
#8

Wow, ended up learning more from this question than I thought I would. It’s good to see the different approaches. I don’t think I’ll ever really get on board with the quoting/macro aspect of the language, but it’s good to get some exposure to it.

1 Like
#9

I got things working, but have a question about updating the vector. My state looks like
{ :size 9
:cells [nil nil nil nil nil nil nil nil nil]
:status: “Next player: X”}

Here’s how I’m updating the cell when clicked on,
(defn update-cell [ndx value]
(fn
(swap! state assoc :cells (assoc (:cells @state) ndx value))))

That seems a bit wordy and kind of ugly. Is there a better way to do it?

#10

I would maybe just change the first assoc to an update.

(swap! state
  update :cells
  assoc 1 :x)

But ideally, don’t have the function depend on global state. You probably want:

(defn update-cell
  [state idx value]
  (update state :cells
    assoc idx value))

And in your on-click code:

(swap! state update-cell 5 :o)
1 Like
#11

OK, awesome! That helps. I went back and forth on the global state versus passing the state around. In real code, I’d pass it around but since it’s throw-away code I went with less typing. :slight_smile:

#12

While the syntax-quote (`) is most commonly used within macros, which are expanded at compile-time, syntax quote itself doesn’t shift the splice to compile-time.

For example, this expression:

`[[email protected]] 

will compile to:

(function (y) {
  return cljs.core.vec.call(null,cljs.core.sequence.call(null,cljs.core.concat.call(null,y)));
})

which is equivalent to:

(vec (sequence (concat y)))

Example: https://www.maria.cloud/gist/c2282b77e2e47d71cb68b3becce8f84c

2 Likes
#13

Hum, it seems you are right. Unquote is expanded, but unquote-splicing just turns itself into a runtime expansion, it won’t evaluate its content at macro-expansion. I checked with Clojure as well, and this is also the case.

So it seems for pre-runtime expansion you need:

#=(vec #=(repeat 9 nil))

Which will do it at read time.

1 Like