Sharing functions between namespaces in Clojure

淺唱寂寞╮ 提交于 2019-12-04 03:02:17

Consider having a look at Zach Tellman's library Potemkin. Zach describes it as "a collection of functions for reorganizing the structure of namespaces and code".

Potemkin isn't without controversy. Here's the start of a thread on the Clojure mailing list where Stuart Sierra is clear that he's not a fan of the idea.

I'm going to answer my question although thank you to everyone who commented: Andrew's answer is very informative and while it doesn't quite answer the question it certainly does lead to answers. I do think Potemkin will do this but I went ahead and wrote my own solution based on this thread. I will say I don't feel this approach is generally idiomatic, based on some responses here and further discussion in IRC, however it may make sense for limited use cases, such as mine.

But to answer the question, this function should do what I had originally intended:

(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)))))

Then you could invoke it something like this, let's say we put this in foo.clj (if you see the gist I added in the edit):

(ns testing.foo)

(immigrate `testing.baz)

Now if we require testing.foo in the REPL:

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

After speaking with Stuart Sierra on IRC and reading the email thread Andrew linked, I came to the conclusion that this is not necessarily the intended way to use namespaces.

Instead a better way to implement my library might look like this:

=> (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")

However given that I want to present the end-user with the cleanest API as possible, I may go ahead and use the immigrate function in this very limited scope of "importing" the HTTP method functions.

Edit:

After further discussion, I think the above solution should not generally be used, as I said already. And for my use-case I'll probably go with my last solution, i.e. using two separate namespaces.

One option to design the solution would be to use multimethods with default implementation provided.

;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 
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!