I\'ve had a look at the algo.monads and fluokitten documentation. I\'ve also read through monad blog entries by Jim Duey, Konrad Hinsen and Leonardo Borges.
The onl
Sure. A Reader
is just a function that takes an environment and extracts some value from it.
With Reader
, m-result
takes some value and produces a reader that ignores the environment and returns that value:
(defn reader-result
[value]
"Ignores environment, returns value"
(fn [env]
value))
m-bind
takes a reader and a function f
that accepts a value and produces a new reader. It then combines those arguments to produce a new reader which applies the initial reader to an environment, feeds the value it produces to f
to produce a new reader, then applies that reader to the environment:
(defn reader-bind
[reader f]
"Applies reader to environment,
then applies f to new environment"
(fn [env]
(let [read-value (reader env)]
((f read-value) env))))
With these functions, we can define Reader
with algo.monads
:
(m/defmonad Reader
[m-result reader-result
m-bind reader-bind])
There are a few important helper functions. run-reader
takes a reader and environment and applies the reader to that environment:
(defn run-reader
"Runs a reader against an environment,
returns the resulting environment"
[reader env]
(reader env))
Since our readers are just functions, run-reader
isn't strictly necessary. However, it can make things clearer and it keeps us closer to the Haskell implementation, so we'll use it going ahead.
ask
and asks
let us examine the environment. ask
is a reader that returns the environment. asks
takes a selector and creates a reader that applies that selector to an environment:
(defn ask
"A reader that returns the environment"
[env]
env)
(defn asks
"A reader that returns the result of
f applied to the environment"
[f]
(fn [env]
(f env)))
This gets us far enough to go through the first Reader example:
(defn lookup-var
[name bindings]
(get bindings name))
(def calc-is-count-correct?
(m/domonad Reader
[binding-count (asks #(lookup-var "count" %))
bindings ask]
(= binding-count (count bindings))))
(defn is-count-correct?
[bindings]
(run-reader calc-is-count-correct? bindings))
(def sample-bindings {"count" 3, "1" 1, "b" 2})
(println
(str "Count is correct for bindings " sample-bindings ": "
(is-count-correct? sample-bindings)))
The other important Reader
function is local
. This takes a function that modifies an environment and a reader and creates a new reader that modifies the environment before passing it to the original reader:
(defn local
[modify reader]
"A reader that modifies the environment
before calling the original reader"
(fn [env]
(run-reader reader (modify env))))
With that, we can go through the second example:
(def calc-content-len
(m/domonad Reader
[content ask]
(count content)))
(def calc-modified-content-len
(local #(str "Prefix " %) calc-content-len))
(let [s "12345"
modified-len (run-reader calc-modified-content-len s)
len (run-reader calc-content-len s)]
(println
(str "Modified 's' length: " modified-len))
(println
(str "Original 's' length: " len)))
So, that's all it takes to make Reader
.