获取调用表单的名称空间

时间:2019-05-12 19:32:38

标签: clojure

我想要一个宏this-ns,以便它返回被调用位置的名称空间。例如,如果我有这段代码

(ns nstest.main
  (:require [nstest.core :as nstest]))

(defn ns-str [x]
  (-> x (.getName) name))

(defn -main [& args]
  (println "The ns according to *ns*:" (ns-str *ns*))
  (println "The actual ns:" (ns-str (nstest/this-ns))))

我希望调用lein run会产生以下输出:

The ns according to *ns*: user
The actual ns: nstest.main

我想到的实现是以下代码:

(ns nstest.core)

(defmacro this-ns []
  (let [s (gensym)]
    `(do (def ~s)
         (-> (var ~s)
             (.ns)))))

它似乎确实有效,但是感觉很hacky。值得注意的是,在上面的示例中,它将扩展到def函数内部{em> 中调用的-main,感觉不太干净。

我的问题:是否有更好的方法来实现this-ns来获得调用this-ns的命名空间?

2 个答案:

答案 0 :(得分:3)

这是另一个变体:

(defmacro this-ns []
  `(->> (fn []) str (re-find #"^.*?(?=\$|$)") symbol find-ns))

问题是匿名函数被编译为一个类似以下名称的类 playground.core$_main$fn__181@27a0a5a2,因此它以编译函数的实际名称空间的名称开头。

不能说它看上去没有那么骇人听闻,然后是您的变体,仍然避免了def在您的情况下引入的副作用。

答案 1 :(得分:2)

有趣的问题。我永远都不会想到您的代码会为第一个println语句输出user

问题在于只有Clojure编译器知道NS的名称,并且仅在编译源文件时才知道。在运行时调用NS中的任何功能之前,这些信息会丢失。这就是为什么我们从代码中获得user的原因:显然lein从demo.core/-main ns调用了user

保存NS信息以便可以在运行时(相对于编译时)进行访问的唯一方法是像在宏中使用def一样,以已知名称强制添加NS。这类似于肖恩的trick俩(来自Carcingenicate's link):

 (def ^:private my-ns *ns*)   ; need to paste this into *each* ns

我唯一想到的另一种方法是以某种方式获取Java调用堆栈,因此我们可以找出谁调用了我们的“ get-ns”函数。当然,Java提供了一种检查调用堆栈的简单方法:

(ns demo.core
  (:use tupelo.core)
  (:require
    [clojure.string :as str]))

(defn caller-ns-func []
  (let [ex                (RuntimeException. "dummy")
        st                (.getStackTrace ex)
        class-names       (mapv #(.getClassName %) st)
        class-name-this   (first class-names)
        class-name-caller (first
                            (drop-while #(= class-name-this %)
                              class-names))

        ; class-name-caller is like "tst.demo.core$funky"
        [ns-name fn-name] (str/split class-name-caller #"\$")]
    (vals->map ns-name fn-name)))

和用法:

(ns tst.demo.core
  (:use demo.core tupelo.core tupelo.test)
  (:require
    [clojure.string :as str]
    [demo.core :as core]))

(defn funky [& args]
  (spyx (core/caller-ns-func)))

(dotest
  (funky))

结果:

(core/caller-ns-func) => {:ns-name "tst.demo.core", :fn-name "funky"}

我们甚至不需要宏!