Как я могу получить имя функции из символа в clojure?
Предположим, я определяю x как функцию символа foo
(defn foo [x] x)
(def x foo)
Может ли имя "foo" быть обнаружено, если дано только x?
Есть ли способ в foo найти имя функции x - "foo" в этом случае?
(foo x)
Есть или есть возможность создать такую функцию как:
(get-fn-name x)
foo
2 ответа
Подобный вопрос был задан недавно на этом сайте; смотрите здесь
Когда вы делаете (def x foo)
, вы определяете х как значение в foo
"не"foo
сама ". Однажды foo
решил его значение, это значение больше не имеет никакого отношения к foo
,
Поэтому, возможно, вы видите один из возможных ответов на ваш вопрос сейчас: не решайте foo
когда вы идете, чтобы определить x
, Вместо того, чтобы делать...
(def x foo)
...делать...
(def x 'foo)
Теперь, если вы попытаетесь получить значение x
, ты получишь foo
(буквально), а не значение, которое foo
решает до.
user> x
=> foo
Тем не менее, это, вероятно, проблематично, потому что вы, возможно, также иногда захотите получить значение, которое foo
решает использовать x
, Однако, однако, вы сможете сделать это, выполнив:
user> @(resolve x)
=> #<user$foo user$foo@157b46f>
Если бы я должен был описать, что это делает, это было бы: "получить значение x
разрешает, использует это как символ, затем разрешает этот символ к его переменной (не его значению) и разыменовывает этот переменный для получения значения ".
... Теперь давайте сделаем что-нибудь хакерское. Я не уверен, что советовал бы делать что-либо из того, что я собираюсь предложить, но вы спросили Can the name "foo" be discovered if only given x?
и я могу думать о двух способах, которыми вы могли бы сделать это.
Метод № 1: пересмотреть имя fn var
Обратите внимание что foo
а также x
оба решают ниже:
(defn foo [a] (println a))
(def x foo)
user> foo
=> #<user$foo user$foo@1e2afb2>
user> x
=> #<user$foo user$foo@1e2afb2>
Теперь проверьте это:
user> (str foo)
=> "user$foo@1e2afb2"
user> (str x)
=> "user$foo@1e2afb2"
Здорово. Это работает только потому, что foo
разрешается в функцию, которая имеет имя, подобное var, имя, которое будет таким же для x
потому что это относится к той же функции. Обратите внимание, что "foo" содержится в строке, созданной (str x)
(а также (foo x)
). Это потому, что имя переменной функции, очевидно, создается с некоторой обратной ссылкой на символ, который использовался для его первоначального определения. Мы собираемся использовать этот факт, чтобы найти этот символ в любой функции.
Итак, я написал регулярное выражение, чтобы найти "foo" внутри этой строки имени функции var. Дело не в том, что он ищет "foo", а в том, что он ищет любую подстроку - в терминах регулярных выражений, ".*"
- которому предшествует \$
характер - в терминах регулярных выражений "(?<=\$)"
-, а затем \@
характер - в терминах регулярных выражений "(?=@)"
...
user> (re-find #"(?<=\$).*(?=@)"
(str x))
=> "foo"
Мы можем далее преобразовать это в символ, просто оборачивая (symbol ...)
вокруг него:
user> (symbol (re-find #"(?<=\$).*(?=@)"
(str x)))
=> foo
Кроме того, весь этот процесс может быть обобщен на функцию, которая будет принимать функцию и возвращать символ, связанный с именем переменной этой функции - то есть символ, который был задан, когда функция была первоначально определена (этот процесс вообще не будет хорошо работать для анонимные функции).
(defn get-fn-init-sym [f]
(symbol (re-find #"(?<=\$).*(?=@)" (str f))))
... или это, что я считаю, лучше читать...
(defn get-fn-init-sym [f]
(->> (str f)
(re-find #"(?<=\$).*(?=@)")
symbol))
Теперь мы можем сделать...
user> (get-fn-init-sym x)
=> foo
Метод № 2: обратный поиск всех ns отображений на основе идентичности
Это будет весело.
Итак, мы собираемся взять все сопоставления пространства имен, а затем dissoc
'x
Исходя из этого, затем отфильтруйте то, что остается, основываясь на том, относится ли значение val в каждом отображении к тому же x
решает до. Мы возьмем первое в этой отфильтрованной последовательности, а затем возьмем ключ в этом первом, чтобы получить символ.
user> (->> (dissoc (ns-map *ns*) 'x)
(filter #(identical? (let [v (val %)]
(if (var? v) @v v))
x))
first
key)
=> foo
Обратите внимание, что если вы заменили x
с foo
выше, вы получите x
, На самом деле все, что это делает, возвращает возвращаемое им имя, которое соответствует тому же значению, что и x
, Как и раньше, это можно обобщить на функцию:
(defn find-equiv-sym [sym]
(->> (dissoc (ns-map *ns*) sym)
(filter #(identical? (let [v (val %)]
(if (var? v) @v v))
@(resolve sym)))
first
key))
Основным отличием здесь является то, что аргумент должен быть символом в кавычках.
user> (find-equiv-sym 'x)
=> foo
это find-equiv-sym
Функция действительно не очень хорошая. Проблемы будут возникать, когда в пространстве имен будет несколько вещей, которые будут иметь одинаковые значения. Вы можете вернуть этот список символов, которые разрешают идентичные вещи (вместо того, чтобы просто возвращать первый), а затем обработать его дальше оттуда. Было бы просто изменить текущую функцию, чтобы сделать эту работу: удалить последние две строки (first
а также key
) и заменить их на (map key)
,
В любом случае, я надеюсь, что это было так же весело и интересно для вас, как и для меня, но я сомневаюсь, что любой из этих хаков будет хорошим способом решения проблем. Я защищаю свое первое решение.
Непонятно, почему вы хотели бы сделать это - когда вы делаете (def x foo)
вы эффективно даете имя x
на новую переменную в вашем пространстве имен. Это имеет то же значение, что и foo
(т.е. он содержит ту же функцию), но в остальном он полностью независим от foo. Это как две ссылки на один и тот же объект, если использовать аналогию с Java.
Почему вы должны продолжать хотеть получить имя foo
?
Если вы действительно хотите сделать что-то подобное, это может быть случай, когда вы можете использовать некоторые пользовательские метаданные для функции, которая содержит исходный символ:
(def foo
(with-meta
(fn [x] x)
{:original-function `foo}))
(def bar foo)
(defn original-function [v]
"Returns the :original-function symbol from the metadata map"
(:original-function (meta v)))
(original-function bar)
=> user/foo