获取调用表单的命名空间

Rul*_*lle 4 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))))
Run Code Online (Sandbox Code Playgroud)

我希望调用lein run会产生这个输出:

The ns according to *ns*: user
The actual ns: nstest.main
Run Code Online (Sandbox Code Playgroud)

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

(ns nstest.core)

(defmacro this-ns []
  (let [s (gensym)]
    `(do (def ~s)
         (-> (var ~s)
             (.ns)))))
Run Code Online (Sandbox Code Playgroud)

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

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

lee*_*ski 5

这是另一种变体:

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

事情是匿名函数被编译成一个名为类似的类 playground.core$_main$fn__181@27a0a5a2,所以它以函数被编译的实际命名空间的名称开头。

不能说它看起来不那么笨拙,那么您的变体,它仍然避免了def在您的情况下引入的副作用。


Ala*_*son 4

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

问题是只有 Clojure 编译器知道 NS 的名称,而且只有在编译源文件时才知道。在运行时调用 NS 中的任何函数之前,此信息就会丢失。这就是为什么我们user从代码中得到:显然是demo.core/-main来自userns 的 lein 调用。

保存 NS 信息以便在运行时(相对于编译时)可访问的唯一方法是强制以已知名称添加到 NS,就像您def在宏中所做的那样。这类似于 Sean 的技巧(来自Carcingenicate 的链接):

 (def ^:private my-ns *ns*)   ; need to paste this into *each* ns
Run Code Online (Sandbox Code Playgroud)

我能想到的唯一其他方法是以某种方式获取 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)))
Run Code Online (Sandbox Code Playgroud)

和用法:

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

结果:

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

我们甚至不需要宏!