Как получить имя функции как строку в 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.