-
Notifications
You must be signed in to change notification settings - Fork 4
Modeling Access to Global State
An alternative to accessing local state is to pass data around as arguments to functions. This helps to keep functions free of side-effects but involves more work, it can make the code more complex, and it doesn't scale well as the data increases. The Reader
monad offers the ability to access data, supplied by its caller, at any point within the flow of control, while hiding away the boilerplate of passing things around. In exchange for boxing values inside the Reader
data type, we get the simplicity of global data from pure functions.
The following code illustrates the mechanics of the Reader construct and use. There are two functions that trivially supply text for printing. A third one collects the text and prints it with a given margin, whose value is to be read as if it were global, though all these functions are pure. The code that runs the reader monad provides the value of the margin.
(defn text1 [] (reader "The quick fox"))
(defn text2 [] (reader "jumped over the lazy dog"))
(defn line []
"makes a line with two parts and a margin"
(monad [part1 (text1)
part2 (text2)
margin ask]
(let [sp (clojure.string/join (repeat margin \space))]
(reader (str sp part1 part2)))))
The monad
macro provides the sequencing of Reader values and their bindings to the local variables. In this case, the variable part1
gets the string boxed in the Reader by function text1
, and part2
gets the text from function text2
. The variable margin
gets the result of ask
which is a predefined function that reads the Reader's environment value, in this case 8. Function line
, in turn, makes a reader off the margin and the two lines of text. That is the Reader to be run by run-reader
.
run-reader
takes a Reader
instance and the initial value for its environment. Callers can only supply a single value, but this may be any Clojure data structure. To complete the above example, here we call run-reader
passing the Reader value return by line
and the value 8 as the environment, which is meant to provide the line's margin. The result is the string created by str
and returned as a Reader value.
(println (run-reader (line) 8))
;; The quick foxjumped over the lazy dog
The monad
macro will always return values of the same kind it works with. In the above example in function line
, it uses Reader values (containing strings) and the environment value. Then it uses the reader
constructor to return the result. Note that run-reader
uses the result of line
called with no arguments. Thus it is not apparent how ask
is able to provide the value 8 to the variable margin
. The answer is that the reader
constructor makes high-order functions, so the one returned by line
is the one that gets the environment value.