Получение пространства имен, где форма называется - PullRequest
2 голосов
/ 12 мая 2019

Я бы хотел макрос 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)))))

Кажется, это работает, но это кажется очень хакерским.Примечательно, что в приведенном выше примере он расширится до def, вызываясь внутри функции -main, которая не выглядит очень чистой.

Мой вопрос : Есть ли лучший способ реализовать this-ns для получения пространства имен, где вызывается this-ns?

Ответы [ 2 ]

3 голосов
/ 13 мая 2019

вот еще один вариант:

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

дело в том, что анонимная функция компилируется в класс с именем, похожим на playground.core$_main$fn__181@27a0a5a2, поэтому она начинается с имени фактического пространства имен, которое функция компилируетin.

Не могу сказать, что это выглядит менее хакерски, чем ваш вариант, тем не менее, он избегает побочного эффекта, введенного def в вашем случае.

2 голосов
/ 13 мая 2019

Интересный вопрос.Я бы никогда не догадался, что ваш код будет выводить user для первого оператора println.

Проблема в том, что только компилятор Clojure знает имя NS, и это только когда исходный файлскомпилирован.Эта информация теряется до вызова каких-либо функций в NS во время выполнения.Вот почему мы получаем user из кода: очевидно, что lein вызывает demo.core/-main из user ns.

Единственный способ сохранить информацию NS, чтобы она была доступна во время выполнения (против времени компиляции)является принудительное добавление к NS под известным именем, как вы сделали с вашим def в макросе.Это похоже на трюк Шона (из ссылки Carcingenicate ):

 (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"}

И мы недаже не нужен макрос!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...