Reputation: 87
I'm trying to change the value of an atom which holds information about the state of a game.
The code is as follows:
(def initial-state {:pos [0 0] :dir go-right})
(defonce app-state (atom initial-state))
(defn go-right [[x y]] [(inc x) y])
(defn new-pos [state] ((:dir @state) (:pos @state))))
(defn update-state [app-state]
(assoc @app-state :pos (new-pos app-state)))
I have a function that should update the :pos
of the atom, based on the function stored in ":dir
".
My problem is that in the new-pos
function, I get an error saying that basically, @state is nil
.
Error:
Uncaught TypeError: Cannot read property 'call' of null
What a I missing?
Upvotes: 2
Views: 494
Reputation: 29989
You are defining your atom before you have declared the go-right
function. When you dereference it, you'll get nil
.
(def app-state (atom { :dir go-right :pos [0 0] }))
(:dir @state) ;; ===> nil
You could re-arrange your code, but I think a better solution would be to use a simpler data type, such as a semantically appropriate keyword.
(def app-state (atom { :dir :right :pos [0 0] }))
Then use it to reference the appropriate function once the atom is dereferenced.
(def movement { :right go-right
:left go-left
:up go-up
:down go-down })
(defn new-pos [state]
(let [dir (:dir state)
pos (:pos state)
move (get movement dir)]
(move pos)))
This way you'll be able to serialize your app-state
atom, which will allow you to save the state to disk and load it again later.
I'd also opt for using a generic movement function, rather than hardcoding for each direction.
(defn move [[dx dy] [x y]]
[(+ dx x) (+ dy y)])
(def movement { :left (partial move [-1 0])
:right (partial move [1 0])
:up (partial move [0 1])
:down (partial move [0 -1]) })
You probably don't want start calling assoc
on the manually deferenced atom either. This will result in a need to use reset
and you can avoid all of that by using swap
in the first place.
We can remove all of the deref calls and let swap
handle them instead. Functions are generally more useful if they just work with plain data. Let the lower level dereferencing happen elsewhere.
(defn update-state [app-state]
(assoc app-state :pos (new-pos app-state)))
Finally, to update the state atom, use swap.
(swap! app-state update-state)
;; ===> {:dir :right, :pos [1 0]}
Complete Code - Works at Clojurescript.net's REPL
(defn move [[dx dy] [x y]]
[(+ dx x) (+ dy y)])
(def movement { :left (partial move [-1 0])
:right (partial move [1 0])
:up (partial move [0 1])
:down (partial move [0 -1]) })
(defn new-pos [state]
(let [dir (:dir state)
pos (:pos state)
move (get movement dir)]
(move pos)))
(defn update-state [app-state]
(assoc app-state :pos (new-pos app-state)))
(def app-state (atom { :dir go-right :pos [0 0] }))
(swap! app-state update-state)
Upvotes: 4