Clojure 命名空间中函数的动态查找

ser*_*gio 5 clojure

我有许多命名空间,每个命名空间都包含一个具有相同名称的函数,例如:

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

(ns test)
(myns.resourceX/create)
(myns.resourceY/create)
Run Code Online (Sandbox Code Playgroud)

(您可以想象有resourceXresourceYresourceZ等。实际create函数最终会发送 HTTP POST 并返回响应,但这在这里并不重要。)

现在,在另一个命名空间中,我想定义一个带有两个参数的函数:资源名称数组(即命名空间名称)和函数名称,例如:

(defn do-verb
   [verb res-type]
   (??))
Run Code Online (Sandbox Code Playgroud)

所以我可以写:

(do-verb :create :resourceX)
Run Code Online (Sandbox Code Playgroud)

与以下效果相同:

(myns.resourceX/create)
Run Code Online (Sandbox Code Playgroud)

我尝试过的一件事是使用ns-resolve,例如:

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

但我不确定是否使用ns-resolve-- 似乎是一种黑客行为。

我探索的另一种可能性是定义一个映射来将符号与函数相关联:

(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))
Run Code Online (Sandbox Code Playgroud)

但这对我来说有一个缺点,即convert-fns每次添加新资源时都需要进行修改。

有没有其他方法可以替代这些方法?

Ole*_*Cat 6

如果你确实想在命名空间中进行动态查找,你可以使用ns-publics函数。

请参阅以下片段:

(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)))
Run Code Online (Sandbox Code Playgroud)

然而,这看起来仍然有点老套,与使用ns-resolve.

因此,我建议您使您的do-verb函数成为多方法。

(defmulti do-verb (fn [verb res-type & args] [verb res-type]))
Run Code Online (Sandbox Code Playgroud)

之后,do-verb针对您的每种verb类型和资源类型实施:

(defmethod do-verb [:create :resourceX] [_ _ & args] ...)
Run Code Online (Sandbox Code Playgroud)

并按如下方式调用它:

(do-verb :create :resourceX ...)
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,签名完美匹配。