Is it possible to do the Reader Monad from Haskell in Clojure?

前端 未结 2 1869
花落未央
花落未央 2021-01-05 09:10

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

2条回答
  •  小鲜肉
    小鲜肉 (楼主)
    2021-01-05 09:44

    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.

提交回复
热议问题