获取调用表单的名称空间 [英] Getting the namespace where a form is called
问题描述
我想要一个宏 this-ns
,以便它返回被调用位置的名称空间.例如,如果我有此代码
I would like a macro this-ns
such that it returns the namespace of the location where it is being called. For instance, if I have this code
(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
会产生以下输出:
I would expect that calling lein run
would produce this output:
The ns according to *ns*: user
The actual ns: nstest.main
我想到的实现是以下代码:
What I came up with as implementation was the following code:
(ns nstest.core)
(defmacro this-ns []
(let [s (gensym)]
`(do (def ~s)
(-> (var ~s)
(.ns)))))
它似乎确实有效,但是感觉很hacky.值得注意的是,在上面的示例中,它将扩展到 -main
函数的内部 内调用的 def
,感觉不太干净.
It does seem to work, but it feels very hacky. Notably, in the above example it will expand to def
being invoked inside the -main
function which does not feel very clean.
我的问题:是否有更好的方法来实现 this-ns
以获取调用 this-ns
的命名空间?>
My question: Is there a better way to implement this-ns
to obtain the namespace where this-ns
is called?
推荐答案
有趣的问题.我永远都不会猜到您的代码会为第一个println语句输出 user
.
Interesting question. I would never have guessed that your code would output user
for the first println statement.
问题在于只有Clojure编译器知道NS的名称,并且仅在编译源文件时才知道.在运行时调用NS中的任何功能之前,这些信息会丢失.这就是为什么我们从代码中获取 user
的原因:显然lein从 user
ns调用 demo.core/-main
.
The problem is that only the Clojure compiler knows the name of an NS, and that is only when a source file is being compiled. This information is lost before any functions in the NS are called at runtime. That is why we get user
from the code: apparently lein calls demo.core/-main
from the user
ns.
保存NS信息以便可以在运行时(相对于编译时)进行访问的唯一方法是强制使用已知名称对NS进行添加,就像您在 def
中所做的那样.宏.这类似于肖恩的技巧(来自
The only way to save the NS information so it is accessible at runtime (vs compile time) is to force an addition to the NS under a known name, as you did with your def
in the macro. This is similar to Sean's trick (from Carcingenicate's link):
(def ^:private my-ns *ns*) ; need to paste this into *each* ns
我唯一想到的另一种方法是以某种方式获取Java调用堆栈,因此我们可以找出谁调用了我们的"get-ns"函数.当然,Java提供了一种检查调用堆栈的简单方法:
The only other approach I could think of was to somehow get the Java call stack, so we could find out who called our "get-ns" function. Of course, Java provides a simple way to examine the call stack:
(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"}
我们甚至不需要宏!
这篇关于获取调用表单的名称空间的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!