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类型系统的约束,使我们能够处理数据和程序以同样的方式,对我们的程序进行任意转换,以实现我们认为合适的语法和控制。