Function Clojure中命名空间之间共享函数

Function Clojure中命名空间之间共享函数,function,clojure,namespaces,abstraction,Function,Clojure,Namespaces,Abstraction,我很可能用错误的方式来处理这个问题,所以请原谅我的天真: 为了学习Clojure,我已经开始将Python的OAuth客户端库移植到Clojure。我通过包装CLJHTTP来实现这一点,就像在Python库中包装Python请求一样。到目前为止,这似乎工作得很好,我真的很高兴看到Clojure的实现变得栩栩如生 但是我遇到了一个问题:我计划同时支持OAuth 1.0和2.0,并将各自的函数拆分为两个文件:oauth1.clj和oauth2.clj。现在,理想情况下,每个文件都应该公开一组与HTT

我很可能用错误的方式来处理这个问题,所以请原谅我的天真:

为了学习Clojure,我已经开始将Python的OAuth客户端库移植到Clojure。我通过包装CLJHTTP来实现这一点,就像在Python库中包装Python请求一样。到目前为止,这似乎工作得很好,我真的很高兴看到Clojure的实现变得栩栩如生

但是我遇到了一个问题:我计划同时支持OAuth 1.0和2.0,并将各自的函数拆分为两个文件:oauth1.clj和oauth2.clj。现在,理想情况下,每个文件都应该公开一组与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)中要求它们,以避免重复编写相同的代码

只要我使用文件中引用的函数,即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
=> (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")
找不到var
oauth2/get
,因为将其拉入oauth2.clj中的名称空间似乎不会像它实际在该名称空间中一样公开它。我不想用更多的函数来包装它们,因为这基本上违背了目的;这些函数非常简单(它们只包装了一个
请求
函数),如果我这样做的话,基本上我会在三个地方编写它们

我确信我并没有在Clojure中正确地探索名称空间,而且可能是以惯用的方式思考抽象问题和代码共享的一般方式

所以我想知道这个问题的惯用解决方案是什么?我是不是走错了路

编辑:

以下是问题的简化:

注意,目标是一次性编写HTTP谓词函数。他们不需要特殊的分派类型或类似的东西。他们已经很好了。问题是它们不会从
accord.oauth1
accord.oauth2
中公开,例如,当您的程序需要
accord.oauth2

如果这是Python,我们可以像这样导入函数:
从accord.core导入get、post、put,
accord.oauth1
accord.oauth2
中,然后当我们使用
accord.oauth1
模块时,我们可以访问所有导入的函数,例如,
将accord.oauth2导入为oauth2
<代码>oauth2.get(…)


我们如何在Clojure中做到这一点,或者我们应该如何习惯性地提供这种干式抽象?

设计解决方案的一个选项是使用提供默认实现的多方法

;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 

考虑看看扎克·泰尔曼的图书馆。Zach将其描述为“用于重新组织名称空间和代码结构的函数集合”


波将金并非没有争议。在Clojure邮件列表中,斯图尔特·塞拉(Stuart Sierra)显然不是这个想法的粉丝。我将回答我的问题,不过感谢大家的评论:安德鲁的回答内容丰富,虽然没有完全回答这个问题,但肯定会得到答案。我确实认为Potemkin会这样做,但我还是继续写了自己的解决方案。我会说,基于这里的一些回应和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中(如果你看到我在编辑中添加的要点):

现在,如果我们需要REPL中的testing.foo:

=> (require '[testing.foo :as foo])
=> (foo/qux "hi!")
;; "hi!"
在与Stuart Sierra就IRC进行了交谈并阅读了Andrew linked之后,我得出结论,这不一定是使用名称空间的预期方式

相反,实现我的库的更好方法可能如下所示:

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

...

=> (oauth2/get my-service "http://example.com/endpoint")  ;; use the HTTP functions
=> (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,我可以继续在“导入”HTTP方法函数的非常有限的范围内使用
immigrate
函数

编辑:


在进一步讨论之后,我认为上述解决方案不应该像我已经说过的那样普遍使用。对于我的用例,我可能会使用我的上一个解决方案,即使用两个单独的名称空间。

“我的第一反应是将这些函数移动到core.clj中,然后在相应的OAuth名称空间(oauth1、oauth2)中要求它们,以避免重复编写相同的代码。”您这样做了吗?如果是,为什么需要在第二个代码块中使用它?第二个代码块在哪个文件/命名空间中?请提供特定的编译器错误。@tieTYT我将HTTP谓词相关函数移到core.clj中,core.clj是
accord.core
命名空间。然后,
accord.oauth1
accord.oauth2
名称空间需要这些函数,方法是将所有函数从
accord.core
引用到各自的名称空间中。这样做的问题是,您不能在REPL或程序中要求
accord.oauth2
,然后以这种方式使用HTTP函数:
accord.oauth2/get
。如果函数在每个文件中实际写入两次,那么就可以了。不过,我正试图避免这种情况。:)我有两个想法。1) 重构代码以拥有第三个名称空间,如
accord.oauth common
,然后导入该名称空间以获得公共函数,或者2)只需使用
def
在每个名称空间中重新绑定所需的函数(而不是完全重新定义