Как получить имя функции как строку в Clojure?
Как вы можете получить имя функции как строку в Clojure?
То, что у меня до сих пор не выглядит нигде ближе к идиоматическому:
(defn fn-name
[f]
(first (re-find #"(?<=\$)([^@]+)([email protected])" (str f))))
(defn foo [])
(fn-name foo) ;; returns "foo"
EDIT: С предоставленными подсказками я собрал базовый макрос, который делает то, что я хочу. Это выглядит лучше?
(defmacro fn-name
[f]
`(-> ~f var meta :name str))
Ответы
Ответ 1
Для функций, определенных с формой defn
:
user> (-> #'map meta :name)
map
user> (defn nothing [& _])
#'user/nothing
user> (-> #'nothing meta :name)
nothing
Для этого требуется доступ к var, а не только значение функции, которое имеет значение var.
Ответ 2
EDIT: я нашел лучший способ
Clojure включает функцию, называемую demunge. Поэтому вместо того, чтобы повторно изобретать колесо, просто используйте его;)
(clojure.repl/demunge (str map?));; "clojure.core/[email protected]"
Или, если вы хотите использовать префиксную версию
(defn- pretty-demunge
[fn-object]
(let [dem-fn (demunge (str fn-object))
pretty (second (re-find #"(.*?\/.*?)[\-\-|@].*" dem-fn))]
(if pretty pretty dem-fn)))
(pretty-demunge map?);; "clojure.core/map?"
Я думаю, что есть более чистый способ сделать это. Я столкнулся с одной и той же проблемой, связанной с желанием узнать имя функции, полученной как аргумент в функции. Я не мог использовать макрос, потому что мне нужно было его сопоставить, поэтому вот что у меня есть: (предположим, что string? - это функция, переданная как аргумент)
(clojure.string/replace (second (re-find #"^.+\$(.+)\@.+$" (str string?)))
#"\_QMARK\_" "?")
; string?
Конечно, это не полное решение, но я уверен, что вы сможете пробиться отсюда. В основном Clojure управляет именем функции во что-то, что он может использовать. Итак, основное, хотя очевидно: вам нужно развязать это!
Это довольно просто, так как str работает над любой функцией: D, возвращая измененное имя функции.
Кстати, это также работает
(def foo string?)
(clojure.string/replace (second (re-find #"^.+\$(.+)\@.+$" (str foo)))
#"\_QMARK\_" "?")
; string?
Удачи
Ответ 3
Я предложил улучшение ответа @carocad в комментарии. Поскольку мне также нужно было это сделать, и мне нужно было сделать это как в clj
, так и в cljs
, вот что я придумал:
(ns my-ns.core
(:require [clojure.string :as s]
#?(:clj [clojure.main :refer [demunge]])))
(defn fn-name
[f]
#?(:clj
(as-> (str f) $
(demunge $)
(or (re-find #"(.+)--\[email protected]" $)
(re-find #"(.+)@" $))
(last $))
:cljs
(as-> (.-name f) $
(demunge $)
(s/split $ #"/")
((juxt butlast last) $)
(update $ 0 #(s/join "." %))
(s/join "/" $))))
Обратите внимание, что cljs.core
имеет свой собственный demunge
встроенный и не может получить доступ к clojure.main
.
Edit
Обратите внимание, что когда вы делаете (with-meta a-fn {...})
, он возвращает clojure.lang.AFunction
в clojure, который скрывает базовое имя и пространство имен. Могут быть и другие подобные ситуации, я не уверен. С fn
литеральными формами вы можете сделать ^{...} (fn ...)
вместо (with-meta (fn ...) {...})
, и он не вернет clojure.lang.AFunction
, но это обходное решение не будет работать с предопределенными функциями. Я не тестировал это в clojurescript, чтобы убедиться, что он работает одинаково.
Обратите внимание, что анонимные функции в clojure всегда заканчиваются на fn
, как в "my-ns.core/fn"
. Обычно эти функции имели бы конец "--[0-9]+"
в конце, но вышеперечисленное выражение удаляет это. Вы можете изменить функцию выше и сделать исключение для анонимных функций. Пока лямбда имеет внутреннее имя, которое будет использоваться, например:
(fn-name (fn abc [x] x)) ;;=> "my-ns.core/abc"
Опять же, я еще не тестировал ни одну из этих заметок в clojurescript.