9

私は間違った方法でこれに取り組んでいる可能性が非常に高いので、私の素朴さを許してください:

Clojure を学ぶために、Python 用の OAuth クライアント ライブラリを Clojure に移植し始めました。これは、Python ライブラリで Python リクエストをラップするのとほぼ同じ方法で clj-http をラップすることによって行っています。これは今のところかなりうまくいっているようで、Clojure で実装が実現するのを見るのは本当に楽しいです。

ただし、問題が発生しました。OAuth 1.0 と 2.0 の両方をサポートする予定で、それぞれの関数を oauth1.clj と oauth2.clj の 2 つのファイルに分割しました。ここで、各ファイルは、理想的には HTTP 動詞に対応する一連の関数を公開する必要があります。

(ns accord.oauth2)

...

(defn get
  [serv uri & [req]]
  ((:request serv) serv (merge req {:method :get :url uri})))

これらの機能は本質的に同一であり、実際、現在 oauth1.clj と oauth2.clj の間で完全に同一です。私の最初の反応は、これらの関数を core.clj に移動し、それぞれの OAuth 名前空間 (oauth1、oauth2) でそれらを要求して、同じコードを 2 回記述しないようにすることでした。

ファイルで参照されている関数、つまり oauth1.clj または oauth2.clj を使用する限り、これは問題ありません。しかし、私が意図しているようにこのライブラリを使用したいとしましょう (ここでは REPL で、代わりにあなたのプログラムで)、次のようなものです:

=> (require '[accord.oauth2 :as oauth2])  ;; require the library's oauth2 namespace

...

=> (oauth2/get my-service "http://example.com/endpoint")  ;; use the HTTP functions

varoauth2/getが見つからないのは、oauth2.clj の名前空間に引き込むだけでは、実際にその名前空間にあるかのように公開されないように見えるためです。基本的に目的を無効にするため、より多くの関数でそれらをラップしたくありません。関数は非常に単純です (関数をラップするだけrequestです)。そうする場合、基本的に 3 つの場所に関数を記述します。

私は Clojure の名前空間を適切に理解していないと確信しています。さらに、抽象化の問題と慣用的なコード共有についての一般的な考え方についても理解していません。

それで、これに対する慣用的な解決策は何だろうと思っていますか?私はこれを完全に間違った方法で行っていますか?

編集:

問題の簡略化は次のとおりです: https://gist.github.com/maxcountryman/5228259

目標は、HTTP 動詞関数を 1 回記述することであることに注意してください。特別なディスパッチ タイプなどは必要ありません。彼らはもうそのままでいい。accord.oauth1問題は、これらがまたはから公開されていないことです。たとえばaccord.oauth2、プログラムで必要なaccord.oauth2場合です。

これが Python の場合は、次のように関数をインポートするだけで済みます: andにモジュールを使用すると、from accord.core import get, post, put, ...インポートされたすべての関数 (例: ... ) にアクセスできます。accord.oauth1accord.oauth2accord.oauth1import accord.oauth2 as oauth2oauth2.get(...)

Clojure でこれを行うにはどうすればよいでしょうか。また、この種の DRY 抽象化を慣用的に提供するにはどうすればよいでしょうか。

4

3 に答える 3

5

Zach Tellman のライブラリPotemkinを参照してください。Zach はそれを「名前空間とコードの構造を再編成するための関数の集まり」と説明しています。

ポチョムキンには論争がないわけではありません。これは、Clojure メーリングリストのスレッドの始まりです。Stuart Sierra は、彼がこのアイデアのファンではないことを明確にしています。

于 2013-03-23T18:48:59.893 に答える
2

コメントしてくれたすべての人に感謝しますが、質問に答えるつもりです.Andrewの答えは非常に有益であり、質問には完全には答えていませんが、確かに答えにつながります. 私はポチョムキンがこれを行うと思いますが、私は先に進み、このスレッドに基づいて独自の解決策を書きました. ここでのいくつかの回答とIRCでのさらなる議論に基づいて、このアプローチは一般的に慣用的ではないと思いますが、私のような限られたユースケースでは意味があるかもしれません.

しかし、質問に答えるために、この関数は私が最初に意図したことを行う必要があります。

(defn immigrate
  [from-ns]
  (require from-ns)
  (doseq [[sym v] (ns-publics (find-ns from-ns))]
    (let [target (if (bound? v)
                  (intern *ns* sym (var-get v))
                  (intern *ns* sym))]
      (->>
        (select-keys (meta target) [:name :ns])
        (merge (meta v))
        (with-meta '~target)))))

次に、次のように呼び出すことができます。これを foo.clj に入れたとしましょう (編集で追加した要点が表示されている場合)。

(ns testing.foo)

(immigrate `testing.baz)

REPL で testing.foo が必要な場合:

=> (require '[testing.foo :as foo])
=> (foo/qux "hi!")
;; "hi!"

IRC で Stuart Sierra と話し、 Andrew がリンクした電子メール スレッドを読んだ後、これは必ずしも名前空間を使用する意図された方法ではないという結論に達しました。

代わりに、私のライブラリを実装するより良い方法は次のようになります。

=> (require '[accord.oauth2 :as oauth2])
=> (def my-serv (oauth2/service 123 456 ...))
=> (require '[accord.http :as http])
=> (http/get my-serv "http://example.com/endpoint")

ただし、エンドユーザーにできるだけクリーンな API を提供したいのでimmigrate、HTTP メソッド関数を「インポート」するという非常に限られた範囲で関数を使用することもできます。

編集:

さらに議論した結果、既に述べたように、上記の解決策は一般的に使用されるべきではないと思います。私のユースケースでは、おそらく最後の解決策、つまり 2 つの別々の名前空間を使用することにします。

于 2013-03-25T14:37:16.047 に答える
0

ソリューションを設計する 1 つのオプションは、提供されているデフォルトの実装でマルチメソッドを使用することです。

;The multi methods which dispatch on type param
(defmulti get (fn [serv uri & [req]] serv))
(defmulti post (fn [serv uri & [req]] serv))

;get default implementation for any type if the type doesn't provide its own implementation
(defmethod get :default [serv uri & [req]]
  "This is general get")

;post doesn't have default implementation and provided specific implementation.
(defmethod post :oauth1 [serv uri & [req]]
  "This is post for oauth1")

(defmethod post :oauth2 [serv uri & [req]]
  "This is post for oauth2")


;Usage
(get :oauth1 uri req) ;will call the default implementation
(get :oauth2 uri req) ;will call the default implementation
(post :oauth1 uri req) ;specific implementation call
(post :oauth2 uri req) ;specific call 
于 2013-03-23T08:28:31.490 に答える