我试图更改包含游戏状态信息的原子的值。
代码如下:
(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)))
我有一个函数应根据存储在" :pos
"中的函数来更新原子的:dir
。
我的问题是在new-pos
函数中,我收到一条错误,说基本上@state是nil
。
错误:
Uncaught TypeError: Cannot read property 'call' of null
我错过了什么?
答案 0 :(得分:4)
在声明go-right
函数之前,您正在定义原子。当您取消引用它时,您将获得nil
。
(def app-state (atom { :dir go-right :pos [0 0] }))
(:dir @state) ;; ===> nil
您可以重新安排代码,但我认为更好的解决方案是使用更简单的数据类型,例如语义上合适的关键字。
(def app-state (atom { :dir :right :pos [0 0] }))
一旦取消引用原子,就用它来引用适当的函数。
(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)))
通过这种方式,您可以序列化app-state
原子,这样您就可以将状态保存到磁盘并稍后再次加载。
我也选择使用通用移动功能,而不是为每个方向进行硬编码。
(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]) })
您可能不想在手动参考原子上开始调用assoc
。这将导致需要使用reset
,您可以首先使用swap
来避免所有这些。
我们可以删除所有deref调用,让swap
代替它们。如果函数只使用普通数据,则函数通常更有用。让较低级别的解除引用发生在其他地方。
(defn update-state [app-state]
(assoc app-state :pos (new-pos app-state)))
最后,要更新state atom,请使用swap。
(swap! app-state update-state)
;; ===> {:dir :right, :pos [1 0]}
完整代码 - 适用于Clojurescript.net的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)