Is it possible to do the Reader Monad from Haskell

2019-02-13 13:26发布

问题:

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 only reference I can find to the Reader Monad in Clojure is this google groups discussion.

My question is: Is it possible to do the Reader Monad from Haskell in Clojure? Could you provide an example?

回答1:

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.



回答2:

There are some fantastic examples of the following monads in Clojure here:

  • the reader monad in clojure
  • the writer monad in clojure
  • the state monad in clojure
  • the identity monad in clojure
  • the maybe monad in clojure
  • the either monad in clojure