读者单调和clojure中的部分功能有什么区别



Leonardo Borges在Clojure的Monads上进行了奇妙的演讲。在其中,他使用以下代码描述了Clojure中的读者单元:

;; 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

我的问题是:读取器monad和clojure中的部分功能有什么区别?

读者monad是一组规则,我们可以应用于干净地撰写读者。您可以使用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)))

请注意,您必须将环境明确地转发给这些读者。总共令人沮丧,对吗?使用读者Monad提供的规则,我们可以获得更清洁的组成:

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

you can 使用 partial来"做"读者monad。通过在let上使用partial在右侧使用环境的CC_6,将let变成do-reader

(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是对读者单元的,因为 let是对身份单元的(此处讨论的关系)。

的确,由于Beyamor在Clojure问题中对您的读者Monad的回答中使用了读者单元的" DO符号"应用,因此与上述do-reader相同的示例将使用。

,但是,为了品种,我将修改第一个示例在环境图中只是更加笨拙,并利用了关键字可以充当函数的事实。

(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中,您确实可以在不适当的情况下获取读者单元的DO指定的功能。类似于对身份单元上的Readert转换,我们可以在let上进行句法转换。当您推测时,一种方法是在环境的部分应用中。

也许更加易被定义的是定义reader->reader->>,将环境分别插入第二个和最后一个参数。我现在将这些作为读者的练习。

从中获得的一个是,尽管Haskell中的类型和类型类具有很多好处,而单调结构是一个有用的想法,没有Clojure类型系统的约束,使我们能够处理数据和程序以同样的方式,对我们的程序进行任意转换,以实现我们认为合适的语法和控制。

相关内容

  • 没有找到相关文章

最新更新