Как получить имя аргумента в 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 ...)
изнутри тела функции. Если это так, то почему вы хотите быть в состоянии сделать это таким образом? Или, если вы чувствуете, что я не ответил на ваш вопрос по какой-то другой причине, что это за причина?