Clojure's model of state and identity

Clojure has an immutable data model. However, this doesn't mean that we can't represent data and change it over time. Direct change or mutation should be avoided at all costs. If necessary, a binding macro will create new var values (or bindings) that are local only to a binding context or scope. These Vars must be marked as ^:dynamic and they should already exist:

(def ^:dynamic a 1)
(def ^:dynamic b 2)

(binding [a 10 b 20] (+ a b))
; => 30

a
; => 1

b
; => 2

It is, however, preferable to choose one of four reference types: Var, Atom, Agent, and Ref. Doing this requires an understanding of Clojure's notion of identity, value, and time. In his presentation Are We There Yet? (you can read more at http://www.infoq.com/presentations/Are-We-There-Yet-Rich-Hickey), Clojure creator, Rich Hickey, describes most computer languages as having variables that allow programmers to set and change a value in the program's runtime. This model assumes, however, that the world stops while you look at or change it (you can read more at http://clojure.org/state). Clojure's approach includes having an immutable value, such as Sally's address, and forever maintaining this value at a point in time. Sally's address, however, is just a value in 2014. Clojure sees identity as a continuous logical entity that has a series of different values over time. So, Sally's home, would be an identity where the value of her address in 2014 may be different from her address in 2015. As we've seen, Clojure's core functions take immutable values and return new values that are updates of the original. The two values share data structures under the hood (Clojure's Persistent Data Structures), which is possible since each value won't change. This succession of data updates is how we disambiguate between values over time. These values often need to be shared with other threads of execution. The Var, Atom, Agent, and Ref reference types are ways of managing shared identities that have a particular state at a given point in time. These reference types can change using a prescribed set of functions to create, update, and reset their state:

  • Refs are used for synchronous, coordinated access to many identities. These functions let you modify their values. You can create a ref using ref, update it using alter or commute, and reset it using ref-set.
    (def one (ref []))
          ;; #object[clojure.lang.Ref 0x1fbb15f {:status       :ready, :val []}]
    
          (dosync
            (alter one conj 12))
          ;; [12]
    
          (deref one)  ;; [12]
          @one  ;; [12]
    
          (dosync
            (ref-set one [:fubar]))
          ;; [:fubar]
    
          @one  ;; [:fubar]
  • Atoms are used for synchronous, uncoordinated access to a single identity. These functions let you modify their values. You can create an atom using atom, update it using swap!, and reset it using reset!:
    (def two (atom []))
          ;; #object[clojure.lang.Atom 0xe2fb45 {:status       :ready, :val []}]
    
          (swap! two conj 12)
          ;; [12]
    
          @two
          ;; [12]
    
          (reset! two [:fubar])
             ;; [:fubar]
    
          @two
          ;; [:fubar]
  • Agents are used for asynchronous, uncoordinated access to a single identity. These functions let you modify their values. You can create an agent using agent, update it using send or send-off, and reset it using restart-agent.
    (send three conj 12)
          ;; #object[clojure.lang.Agent 0x1198a8c {:status       :ready, :val []}]
    
          @three
          ;; [12]
    
          (restart-agent three [:fubar])
          java.lang.RuntimeException
             Agent does not need a restart
             Util.java:  221  clojure.lang.Util/runtimeException
             Agent.java:  210 clojure.lang.Agent/restart
             core.clj: 2078 clojure.core/restart-agent
             RestFn.java:  425 clojure.lang.RestFn/invoke
             six.clj:   66 edgaru.six.six/eval14571
    
          (send three inc)
          java.lang.ClassCastException 
             clojure.lang.PersistentVector cannot be cast to          java.lang.Number
    
          (restart-agent three [:fubar])
          ;; [:fubar]
    
          @three
          ;; [:fubar]
  • Vars are used for isolated (or thread local) changes made to identities with a shared default value. These functions let you modify their values. You can create Vars using def, update them using alter-var-root, and reset them using var-set:
    (def four [])
          ;; []
    
          (alter-var-root (var four) conj 12)
          ;; [12]
    
          four
          ;; [12]
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.143.235.23