2

Leonardo Borges は、Clojure のモナドに関する素晴らしいプレゼンテーションをまとめました。その中で彼は、次のコードを使用して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

私の質問は次のとおりです。リーダー モナドと Clojure の部分関数の違いは何ですか?

4

2 に答える 2

4

リーダーモナドは、リーダーをきれいに構成するために適用できる一連のルールです。これを使用partialしてリーダーを作成することはできますが、それらを組み合わせる方法は実際にはありません。

たとえば、読み取った値を 2 倍にするリーダーが必要だとします。あなたはpartialそれを定義するために使用するかもしれません:

(def doubler
  (partial * 2))

また、読み取った値に 1 を追加するリーダーが必要な場合もあります。

(def plus-oner
  (partial + 1))

ここで、これらの人を 1 つのリーダーにまとめて、結果を追加したいとします。おそらく次のような結果になるでしょう。

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

これらのリーダーに環境を明示的に転送する必要があることに注意してください。完全に残念ですよね?リーダー モナドによって提供されるルールを使用すると、よりクリーンな構成を得ることができます。

(def super-reader
  (do-m reader-m
    [x doubler
     y plus-oner]
    (+ x y)))
于 2014-03-08T05:09:30.300 に答える
3

リーダーモナドを「実行」するために使用できます。右側の環境を適用してpartial構文let変換do-readerを行うことで に変わります。letpartial

(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です (関係についてはここで説明します)。

実際、 Clojure question の読者モナドに対する Beyamor の回答では、読者モナドの「do 記法」アプリケーションのみが使用されているため、同じ例が上記のようにm/domonad Reader置き換えられてdo-reader機能します。

しかし、多様性を持たせるために、最初の例をもう少し Clojury 風に変更して、環境マップを使用し、キーワードが関数として機能できるという事実を利用します。

(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

2 番目の例

(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 では、モナドを適切に導入しなくても、reader モナドの do 表記の機能を実際に取得できます。恒等モナドで ReaderT 変換を行うのと同じように、 で構文変換を行うことができますlet。ご想像のとおり、これを行う 1 つの方法は、環境を部分的に適用することです。

おそらくよりクロジュリッシュなのは、a を定義し、reader->構文reader->>的に環境を 2 番目と最後の引数としてそれぞれ挿入することです。これらは、今のところ読者の演習として残しておきます。

ここからわかることの 1 つは、Haskell の型と型クラスには多くの利点があり、モナド構造は便利なアイデアですが、Clojure では型システムの制約がないため、データとプログラムを同じように扱うことができるということです。プログラムに任意の変換を行い、適切と思われる構文と制御を実装します。

于 2014-03-09T21:33:17.803 に答える