Reputation: 5924
I'm developing a library in clojure, which - in a way - needs to be stateful. In order to not spending too many word in abstract descriptions, here would be an OOP example of how I'd imagine the API of the library could be used.
mylib = new lib();
state1 = mylib.getState();
mylib.continue();
state2 = mylib.getState();
mylib.continue();
state3 = mylib.getState();
[..]
(whereas obviously state1 != state2 != state3)
Ok, how could this be done in a functional language like Clojure?
One approach, that comes to my mind:
(require '(lib.core :as mylib))
(def state1 (mylib/start-state))
(def state2 (mylib/continue state1))
(def state3 (mylib/continue state2))
[..]
This approach does not allocate the state-keeping to the library-side. The problem I have with this is the following: my state does keep information, that should be public to the API user. But also it keeps information that is important for the generation of the next state, which is however not relevant to the public.
Well, there could be another function (mylib/extract-relevant-data state1)
which could postprocess the state in oder for a "clean usage".
I would be really intersted in learning in which ways I could approach this in Clojure.
Upvotes: 2
Views: 69
Reputation: 1447
Method 1: reimplement OOP. I'm treating last-time-called
as private data and relevant state
as what you want to show to people.
(defn start-state []
(let [last-time-called (atom (new java.util.Date))
relevant-state (atom 1)
private-update (fn [] (swap! last-time-called (fn [_] (new java.util.Date))))
get-state (fn [] (do (private-update)
@relevant-state))
continue (fn [] (do (private-update)
(swap! relevant-state inc)
nil))]
{:get-state get-state :continue continue}))
Demonstration:
stack-prj.hiddenState> (def mylib (start-state))
#'stack-prj.hiddenState/mylib
stack-prj.hiddenState> ((mylib :get-state))
1
stack-prj.hiddenState> ((mylib :continue))
nil
stack-prj.hiddenState> ((mylib :get-state))
2
stack-prj.hiddenState> ((mylib :continue))
nil
Note that get-state
and continue
have access to last-time-called
, if they need it.
Method 2: pure functions on simple data.
(defn new-lib []
{:relevant-state 1
:last-time-called (new java.util.Date)})
(defn get-state [lib]
(lib :relevant-state))
(defn lib-continue [lib]
{:relevant-state (inc (lib :relevant-state))
:last-time-called (new java.util.Date)})
Demonstration:
stack-prj.noHiddenState> (def mylib (new-lib))
#'stack-prj.noHiddenState/mylib
stack-prj.noHiddenState> (get-state mylib)
1
stack-prj.noHiddenState> (def ml2 (lib-continue mylib))
#'stack-prj.noHiddenState/ml2
stack-prj.noHiddenState> (get-state ml2)
2
Note that with method 2, you cannot update the object's private variables when the user accesses the state via get-state
. If you need this functionality, then method 1 meets your needs better; if you don't, then method 2 offers more clean, idiomatic, and maintainable code than the first method.
Upvotes: 2