我想要一个宏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
的命名空间?
答案 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"}
我们甚至不需要宏!