Skip to content

Atom

A def fixes a value when you type it. An atom holds a value that can change while the robot runs, and every REPL connection and every loop sees the change. That is what makes live tuning possible.

(atom x) creates one, and def gives it a name:

user=> (def kp (atom 0.02))
#'user/kp
user=> @kp
0.02

@x is shorthand for (deref x): it reads the current value.

defonce

When you paste your code again after an edit, def would put every atom back to its starting value. defonce defines a name only if it is not defined yet, and does not even evaluate the expression when it is:

user=> (defonce tuned (atom 0.5))
#'user/tuned
user=> (reset! tuned 0.9)
0.9
user=> (defonce tuned (atom 0.5))   ; pasting it again leaves it alone
nil
user=> @tuned
0.9

So the atoms you tuned keep their values, while defn still picks up the new code. Use def instead of defonce when you want pasting the code again to reset a value.

reset!

(reset! a v) replaces the value and returns the new one:

user=> (reset! kp 0.03)
0.03
user=> @kp
0.03

swap!

(swap! a f args...) sets the atom to (f @a args...):

user=> (def pieces (atom 0))
#'user/pieces
user=> (swap! pieces inc)
1.0

swap! is atomic. Two connections, or a connection and a running loop, can update the same atom at once and no update is lost. Never do (reset! a (inc @a)) for this: between the read and the write another update could slip in.