如何在Clojure中将函数的名称作为字符串?

时间:2014-03-01 15:25:41

标签: clojure

如何在Clojure中将函数的名称作为字符串?

到目前为止我所看到的并不像惯用语那样:

(defn fn-name
  [f]
  (first (re-find #"(?<=\$)([^@]+)(?=@)" (str f))))

(defn foo [])

(fn-name foo) ;; returns "foo"

编辑:通过提供的提示,我将基本宏放在一起,完成了我想要的工作。它看起来更好吗?

(defmacro fn-name
  [f]
  `(-> ~f var meta :name str))

4 个答案:

答案 0 :(得分:8)

对于使用defn形式定义的函数:

user> (-> #'map meta :name)
map
user> (defn nothing [& _])
#'user/nothing
user> (-> #'nothing meta :name)
nothing

这需要访问var,而不仅仅是var保存的函数值。

答案 1 :(得分:4)

编辑:我找到了更好的方法 Clojure包含一个名为demunge的函数。因此,不要重新发明轮子,只需使用它;)

(clojure.repl/demunge (str map?));; "clojure.core/map?@2b68895c"

或者如果你想要一个美化版本

(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?"

我认为有更简洁的方法可以做到这一点。我遇到了同样的问题,想要知道函数的名称作为函数中的参数获得。我无法使用宏,因为我需要映射它,所以这里是我得到的:(假设字符串?是作为参数传递的函数)

(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?

玩得开心

答案 2 :(得分:1)

我建议在评论中改进@ carocad的回答。既然我也需要这样做,而且我需要在cljcljs中执行此操作,这就是我想出的内容:

(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 #"(.+)--\d+@" $)
                (re-find #"(.+)@" $))
            (last $))
     :cljs
      (as-> (.-name f) $
            (demunge $)
            (s/split $ #"/")
            ((juxt butlast last) $)
            (update $ 0 #(s/join "." %))
            (s/join "/" $))))

请注意cljs.core内置了demunge,无法访问clojure.main

修改

请注意,执行(with-meta a-fn {...})时,它会在clojure中返回clojure.lang.AFunction,它会隐藏基础名称和命名空间信息。可能还有其他情况,我不确定。使用fn文字表单,您可以^{...} (fn ...)代替(with-meta (fn ...) {...}),但它不会返回clojure.lang.AFunction,但该解决方法无法使用预定义工作功能。我还没有在clojurescript中测试任何这个,看它是否以同样的方式工作。

另请注意,clojure中的匿名函数将始终以fn结尾,与"my-ns.core/fn"中一样。通常这些函数最后会有"--[0-9]+",但上面的正则表达式会删除它。您可以修改上面的函数并为匿名函数设置一个例外。只要lambda有一个内部名称,就会使用它,例如:

(fn-name (fn abc [x] x)) ;;=> "my-ns.core/abc"

同样,我还没有在clojurescript中测试过这些笔记。

答案 3 :(得分:0)

我目前正在做的从回溯中提取函数名称如下:

[ERROR] Failed to execute goal on project get-content: Could not resolve dependencies for project project:cf:jar:1.0: Failed to collect dependencies at project:pipeline:jar:0.0.1: Failed to read artifact descriptor for project:pipeline:jar:0.0.1: Could not transfer artifact project:pipeline:pom:0.0.1 from/to project-nexus (XXX): Cannot access XXX with type default using the available connector factories: BasicRepositoryConnectorFactory: Cannot access XXX using the registered transporter factories: WagonTransporterFactory: Unsupported transport protocol -> [Help 1]

这似乎有效。我很感激这不是您所要求的,但可能有用。

已编辑

但这更干净,效果同样好:

(defn remove-anon
  "Remove anonomous function elements from a `munged` name."
  [^String munged]
  (remove #(starts-with? % "fn__") 
          (split munged #"[\.\$]")))

(defn fn-name
  "De-mung a `munged` function name"
  [^String munged]
  (replace (last (remove-anon munged)) "_" "-"))

(defn recover-function-name
  [^StackTraceElement frame]
  (when (ends-with? (.getFileName frame) ".clj")
    (fn-name (.getClassName frame))))