5

I have a number of namespaces each of which contains a function with the same name, e.g.:

(ns myns.resourceX
  ...)
(defn create
  (println "resourceX/create"))

(ns test)
(myns.resourceX/create)
(myns.resourceY/create)

(You can imagine having resourceX, resourceY, resourceZ, etc. The actual create functions end up sending HTTP POSTs and returning the response, but this does not matter here.)

Now, in another namespace, I would like to define a function that takes two arguments: an array of resource names (i.e., namespace name) and function name, e.g.:

(defn do-verb
   [verb res-type]
   (??))

So I can write:

(do-verb :create :resourceX)

to the same effect as:

(myns.resourceX/create)

One thing I tried is using ns-resolve, e.g.:

(defn do-verb [verb res-type & params] (apply (ns-resolve (symbol (clojure.string/join ["myns." (name res-type)])) (symbol (name verb))) params))

But I am unsure about using ns-resolve -- seems like a hack.

Another possibility I have explored is defining a map to associate symbols to functions:

(def convert-fns
  {:resourceX {:create resourceX/create}
   :resourceY {:create resourceY/create}
  ...})

(defn do-verb [verb res-type & params]
  (apply (get-in convert-fns [res-type verb]) params))

But this has the downside, for me, of requiring to modify convert-fns each time a new resource is added.

Are there any alternative approaches to these?

2 Answers 2

6

If you really want to do dynamic lookup in namespaces, you may use ns-publics function.

See the following snippet:

(defn do-verb [verb res-type & params]
  (let [ns-symbol (symbol (str "myns." (name res-type)))
        publics (ns-publics ns-symbol)

        ;; Looking up function we need
        func (publics (symbol (name verb)))]
    (apply func params)))

However, this still seems a bit hacky, same as with usage of ns-resolve.

Therefore, I'd suggest you to make your do-verb function a multimethod.

(defmulti do-verb (fn [verb res-type & args] [verb res-type]))

After, implement do-verb for each of yours verbs and resource types:

(defmethod do-verb [:create :resourceX] [_ _ & args] ...)

And call it as follows:

(do-verb :create :resourceX ...)

As you can see signatures match perfectly.

Sign up to request clarification or add additional context in comments.

4 Comments

thats a nice approach, one thing about this which I like is that you could define (defmethod do-verb :default [_ _ & args] ...) as a 'catch-undefined' clause
As you say, ns-publics sounds as "hacky" as ns-resolve. defmethod, while an interesting approach, suffers from the requirement of having to add a new specialisation for every new resource type... but thanks a lot anyway!
@sergio I believe that "ns-publics" approach is the least hacky we can get, because it does exactly what is written in title of this question: dynamically looks up the function in namespace.
it might well be the case... I will wait a little more before accepting an answer. Could you elaborate a bit on the difference you see between ns-resolve and ns-publics? Thanks.
3

a macro should be able to do that,

But be very careful with the following code - I never written a (sensible) macro before, so while this seems to work it is not tested and there'll probably be gotchas:

Also note that "user." is hardcoded.

; in user ns
user=> (defmacro do-verb [n r] 
         (list (symbol (str "user." (name r) "/" (name n)))))

; quick test
user=> (macroexpand '(do-verb :x :b))
; (user.b/x)

; create a fn in user.b
user.b=> (defn x [] "yay")

; execute new macro
user=> (do-verb :x :b) ; calls (user.b/x)
"yay"

1 Comment

this works -- it works even it do-verb is defined as a function and not as a macro. this feels even more hacky than using ns-resolve, to say the truth. in any case, thank you very much.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.