【问题标题】:How to get function's name as string in Clojure?如何在 Clojure 中获取函数名作为字符串?
【发布时间】:2014-03-01 15:25:40
【问题描述】:

如何在 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))

【问题讨论】:

  • 这个宏有什么意义?它只能在文字上工作,比如(fn-name inc),而(let [f inc] (fn-name f)) 会失败。如果您按字面意思输入inc,您可以直接输入"inc" 并保存一些字符!
  • 这是它的局限性。正如@noisesmith 提到的,它适用于来自同一命名空间的defns。将函数名称也键入为字符串不是我的选择。我正在寻找如何获取作为参数传递的任何函数的名称。
  • 这样的函数有名字吗?
  • 没有。正如 Sean Devlin (groups.google.com/d/msg/clojure/ORRhWgYd2Dk/kxauUUhQjbsJ) 所写,函数对象没有名称。只有函数绑定的符号有一个。我本可以让我的问题更清楚。

标签: clojure


【解决方案1】:

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

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

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

【讨论】:

  • 在这种情况下,对 var 的访问受到限制,因为这意味着我无法将此类代码包装在函数中。但是,根据您的提示(以及其他地方的提示),我创建了一个可以完成这项工作的宏。请参阅我编辑的问题。您如何看待这样的解决方案?
  • 它看起来很合理,只要你没有像 (fn [f] (fn-name f)) 这样的场景,因为 var 将在参数评估点被解析。
  • 没错,我知道这一点,我想知道是否可以通过传递带有“完全限定”函数名的宏来解决这个问题。
  • 什么是#'map?我知道 #{} 表示集合,而 ' 是引号。
  • #'foo 是一个扩展为(var foo) 的读取器宏 - 它访问命名空间拥有的可变变量,而不是存储在其中的值。一般# 引入阅读器宏#() 用于函数,#{} 用于集合,#inst 用于日期等。
【解决方案2】:

编辑:我找到了更好的方法 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?

玩得开心

【讨论】:

  • 哦,我刚刚意识到这与您一开始的情况几乎相同。我不明白它有什么问题。它看起来对我来说非常好。
  • 正则表达式 #"(.*?\/.*?)[\-\-|@].*" 在第一个 - 处停止。例如,(pretty-demunge assoc-in) 返回clojure.core/assoc。如果您只是在@ 之前获得所有内容,我认为主要的边缘情况是字符串以#"--[0-9]+" 结尾(这是由lambdas 引起的,例如当一个函数由(def my-fn (fn ...)) 定义时)。所以你需要检查两种可能性:(last (or (re-find #"(.+)--\d+@" dem-fn) (re-find #"(.+)@" dem-fn))).
【解决方案3】:

我在评论中建议改进@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 中测试过这些注释。

【讨论】:

    【解决方案4】:

    我目前从回溯中提取函数名称的操作如下:

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

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

    已编辑

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

    (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]
      (when (re-find #"\$" munged)
        (replace (last (remove-anon munged)) "_" "-")))
    
    (defn recover-function-name
      [^StackTraceElement frame]
      (when (ends-with? (.getFileName frame) ".clj")
        (fn-name (.getClassName frame))))
    

    【讨论】:

      【解决方案5】:

      在 clojurescript 中,我使用以下内容:

      (defn fn-name [f]
        (let [s (str f)
              s (subs s 9 (clojure.string/index-of s "("))]
          (second (re-matches #"^.*\$([^\$]+)$" s))))
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2010-12-06
        • 1970-01-01
        • 1970-01-01
        • 2011-11-17
        • 2012-03-27
        • 2011-10-31
        • 2018-01-07
        • 1970-01-01
        相关资源
        最近更新 更多