Как получить имя аргумента в Clojure?

Я хотел бы получить имя переменной, определенной вне функции, внутри функции. Имя должно быть именем, которое я использовал в рамках исходного определения, а не вложенными привязками, где я на самом деле пытаюсь использовать имя.

Поэтому я хотел бы сделать что-то вроде (академический пример):

(defn f1 [x1] (println "hello, you passed var name >>" (get-var-name x1) "<<")
(defn f2 [x2] (f1 x2))
(defn f3 [x3] (let [zzz x3] (f2 zzz))
(def my-var 3.1414926)
(f3 my-var)
user> hello, you passed var name >>my-var<<

Я могу сделать этот макрос на основе некоторых вещей, которые я нашел:

(defmacro get-var-name [x]
  `(:name (meta (var ~x))))

Это работает при вызове, например, из REPL, но компилятор дросселируется при вызове из "внутренней" области видимости, например

(defn another-func [y]
  (get-var-name y))

Компилятор говорит: "Невозможно разрешить переменную y". (macroexpand...) показывает, что он пытается найти локальную переменную y в текущем пространстве имен, а не исходную переменную в текущем пространстве имен. Я думаю (var...) ищет только переменные пространства имен, поэтому это препятствует работе макроса внутри функции или другой привязки, такой как let,

Я думаю, что застрял в необходимости вручную получить имя переменной из той же области, где я определяю переменную, и передать ее в качестве дополнительного параметра. Есть ли более элегантный способ передать информацию об имени var через цепочку привязок в точку, где она используется? Это было бы плохо.

Спасибо

3 ответа

Решение

Невозможно получить имя переменной, используемой во внешней области видимости внутри функции - функция получает только значение, переданное в качестве параметра во время выполнения, а не саму переменную.

Единственное, что вы можете сделать, это использовать макросы вместо функций на каждом уровне. Это позволяет вам передавать саму var через различные макросы во время компиляции:

(defmacro f1 [x1] `(println "hello, you passed var name >>" ~(str x1) "<<"))
(defmacro f2 [x2] `(f1 ~x2))
(defmacro f3 [x3] (let [zzz x3] `(f2 ~zzz)))

(f3 my-var)
=> hello, you passed var name >> my-var <<

Это довольно уродливо - вы, конечно, не хотите писать весь свой код с помощью макросов, просто чтобы получить эту функцию! Это может иметь смысл, хотя и в некоторых специализированных обстоятельствах, например, если вы создаете какую-то DSL на основе макросов.

Вы можете передать фактическую переменную в функцию, а не разрешенную переменную, используя #' читатель макрос, как показано ниже:

user=> (defn f1 [x1] (println "hello, you passed var name >>" (:name (meta x1)) "<<"))
#'user/f1
user=> (defn f2 [x2] (f1 x2))
#'user/f2
user=> (defn f3 [x3] (let [zzz x3] (f2 zzz)))  
#'user/f3
user=> (def my-var 3.1414926)
#'user/my-var
user=> (f3 #'my-var)
hello, you passed var name >> my-var <<

Если вы хотите, чтобы значение связывалось с var, вы можете использовать var-get Функция для этого.

Почему бы просто не пройти (get-var-name _symbol-here_) к функциям, где вы хотите использовать имя var внутри тела? Например, используя точно такие же определения get-var-name, f2, f3, а также my-var который вы дали выше (но меняющийся f1 немного):

(defmacro get-var-name [x]
  `(:name (meta (var ~x))))
(defn f1 [x1] (println "hello, you passed var name >>" x1 "<<"))
(defn f2 [x2] (f1 x2))
(defn f3 [x3] (let [zzz x3] (f2 zzz)))
(def my-var 3.1414926)
=> (f3 (get-var-name my-var))
hello, you passed var name >> my-var <<
=> nil

Возможно, иногда вам также понадобится обратиться к значению, на которое ссылается символ в теле функции. Например, скажем, в f1 Вы также хотите распечатать значение, на которое ссылается это имя переменной. Вот как вы можете это сделать:

(defn f1 [x1] (println "hello, you passed var name >>" x1 "<<"
                       "\nwhich refers to value >>" @(resolve x1) "<<"))
=> (f3 (get-var-name my-var))
hello, you passed var name >> my-var << 
which refers to value >> 3.1414926 <<
=> nil

Обратите внимание @(resolve x1) - это то, что возвращает значение, которое my-var относится к (и в свою очередь my-var это значение, на которое ссылается x1).

Кроме того, я хочу отметить, что ваша текущая реализация get-var-name будет выдавать исключения, когда переданный ему аргумент либо не является символом, либо является символом, но в настоящее время не связан со значением. Это поведение, которое вы хотите?

Если то, что я предлагаю, не отвечает на ваш вопрос, то кажется, что вы не хотите проходить (get-var-name _symbol-here_) к функциям, которые могут в конечном итоге использовать имя var, но по какой-то причине действительно хотят иметь возможность делать (get-var-name ...) изнутри тела функции. Если это так, то почему вы хотите быть в состоянии сделать это таким образом? Или, если вы чувствуете, что я не ответил на ваш вопрос по какой-то другой причине, что это за причина?

Другие вопросы по тегам