【问题标题】:What is the difference between the reader monad and a partial function in Clojure?阅读器 monad 和 Clojure 中的部分函数有什么区别?
【发布时间】:2014-04-11 10:16:34
【问题描述】:

Leonardo Borges hasa fantastic presentation 放在 Clojure 中的 Monads 上。在其中他描述了 Clojure 中的 reader monad using the following code

;; Reader Monad

(def reader-m
  {:return (fn [a]
             (fn [_] a))
   :bind (fn [m k]
           (fn [r]
             ((k (m r)) r)))})

(defn ask  []  identity)
(defn asks [f]
  (fn [env]
    (f env)))

(defn connect-to-db []
  (do-m reader-m
        [db-uri (asks :db-uri)]
        (prn (format "Connected to db at %s" db-uri))))

(defn connect-to-api []
  (do-m reader-m
        [api-key (asks :api-key)
         env (ask)]
        (prn (format "Connected to api with key %s" api-key))))

(defn run-app []
  (do-m reader-m
        [_ (connect-to-db)
         _ (connect-to-api)]
        (prn "Done.")))

((run-app) {:db-uri "user:passwd@host/dbname" :api-key "AF167"})
;; "Connected to db at user:passwd@host/dbname"
;; "Connected to api with key AF167"
;; "Done."

这样做的好处是您以纯粹的函数方式从环境中读取值。

但是这种方法看起来非常类似于 Clojure 中的偏函数。考虑以下代码:

user=> (def hundred-times (partial * 100))
#'user/hundred-times

user=> (hundred-times 5)
500

user=> (hundred-times 4 5 6)
12000

我的问题是:Reader monad 和 Clojure 中的偏函数有什么区别?

【问题讨论】:

    标签: clojure monads partial-application reader-monad


    【解决方案1】:

    reader monad 是一组规则,我们可以应用它来干净地编写 reader。您可以使用partial 来制作读者,但它并没有真正为我们提供将它们放在一起的方法。

    例如,假设您想要一个读取值翻倍的阅读器。您可以使用partial 来定义它:

    (def doubler
      (partial * 2))
    

    您可能还希望阅读器在读取的任何值上加一个:

    (def plus-oner
      (partial + 1))
    

    现在,假设您想将这些人组合在一个阅读器中,以添加他们的结果。你可能会得到这样的结果:

    (defn super-reader
      [env]
      (let [x (doubler env)
            y (plus-oner env)]
        (+ x y)))
    

    请注意,您必须将环境显式转发给这些读者。完全无赖,对吧?使用 reader monad 提供的规则,我们可以得到更清晰的组合:

    (def super-reader
      (do-m reader-m
        [x doubler
         y plus-oner]
        (+ x y)))
    

    【讨论】:

      【解决方案2】:

      可以使用partial 来“做”阅读器单子。通过对let 进行句法转换,将let 转换为do-reader,并在右侧应用partial 环境。

      (defmacro do-reader
        [bindings & body] 
        (let [env (gensym 'env_)
              partial-env (fn [f] (list `(partial ~f ~env)))
              bindings* (mapv #(%1 %2) (cycle [identity partial-env]) bindings)] 
          `(fn [~env] (let ~bindings* ~@body))))
      

      那么do-reader 是读者monad,就像let 是identity monad(关系讨论here)。

      确实,由于在Beyamor's answer to your reader monad in Clojure question 中仅使用了阅读器 monad 的“do notation”应用程序,因此将m/domonad Reader 替换为上述do-reader 的相同示例将起作用。

      但是,为了多样化,我将修改第一个示例,使其在环境映射方面更加 Clojurish,并利用关键字可以充当函数这一事实。

      (def sample-bindings {:count 3, :one 1, :b 2})
      
      (def ask identity)
      
      (def calc-is-count-correct? 
        (do-reader [binding-count :count 
                    bindings ask] 
          (= binding-count (count bindings))))
      
      (calc-is-count-correct? sample-bindings)
      ;=> true
      

      第二个例子

      (defn local [modify reader] (comp reader modify))
      
      (def calc-content-len 
        (do-reader [content ask] 
          (count content)))
      
      (def calc-modified-content-len
        (local #(str "Prefix " %) calc-content-len))
      
      (calc-content-len "12345")
      ;=> 5
      
      (calc-modified-content-len "12345")
      ;=> 12
      

      请注意,由于我们建立在 let 之上,我们仍然可以使用破坏。愚蠢的例子:

      (def example1 
        (do-reader [a :foo
                    b :bar] 
          (+ a b)))
      
       (example1 {:foo 2 :bar 40 :baz 800})
       ;=> 42
      
       (def example2 
         (do-reader [[a b] (juxt :foo :bar)]
           (+ a b)))
      
      (example2 {:foo 2 :bar 40 :baz 800})
      ;=> 42
      

      因此,在 Clojure 中,您确实可以在不适当引入 monad 的情况下获得 reader monad 的 do 表示法的功能。类似于对身份单子进行 ReaderT 转换,我们可以对 let 进行句法转换。正如您所推测的,一种方法是部分应用环境。

      也许更多的 Clojurish 会定义一个 reader->reader->> 以分别在语法上将环境插入为第二个和最后一个参数。我暂时将这些作为练习留给读者。

      从中得出的一个结论是,虽然 Haskell 中的类型和类型类有很多好处,并且 monad 结构是一个有用的想法,但在 Clojure 中没有类型系统的约束允许我们处理数据和程序以同样的方式对我们的程序进行任意转换,以实现我们认为合适的语法和控制。

      【讨论】:

      • 这是一种有用的思考方式
      猜你喜欢
      • 2011-01-26
      • 2017-12-28
      • 2017-10-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-08-25
      • 2016-12-17
      • 1970-01-01
      相关资源
      最近更新 更多