获取调用表单的名称空间 [英] Getting the namespace where a form is called

查看:38
本文介绍了获取调用表单的名称空间的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想要一个宏 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆