如何从clojure中的符号中获取函数的名称?

时间:2012-08-11 01:05:41

标签: function clojure symbols name-lookup

假设我将x定义为符号函数foo

(defn foo [x] x)

(def x foo)

如果仅给出x?

,是否可以发现名称“foo”

在foo中是否有办法查找函数x的名称 - 在这种情况下为“foo”?

(foo x)

是否可以创建如下的函数:

(get-fn-name x)
foo

2 个答案:

答案 0 :(得分:8)

最近在这个网站上提出了一个类似的问题;见here

执行(def x foo)时,您将x定义为“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解析为,将其用作符号,然后将该符号解析为其var( not 值),并取消引用var以获得值“。

...现在让我们做一些hacky。我不确定我会建议做其他任何我要建议的事情,但你确实问Can the name "foo" be discovered if only given x?,我可以想到两种方法可以做到。< / p>

方法#1:正则表达式fn var名称
请注意foox都解析为以下内容:

(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))生成的字符串中。这是因为函数的var名称显然是通过对用于最初定义它的符号的一些向后引用而创建的。我们将使用这个事实从任何函数中找到这个符号。

所以,我写了一个regular expression来找到函​​数var name的字符串中的“foo”。它并不是它寻找“foo”,而是它寻找任何子字符串 - 在正则表达式中,".*" - 前面有一个\$字符 - 在正则表达式中条款"(?<=\$)" - 后跟\@字符 - 正则表达式"(?=@)" ...

user> (re-find #"(?<=\$).*(?=@)"
               (str x))
=> "foo"

我们可以通过简单地在其周围包裹(symbol ...)来进一步将其转换为符号:

user> (symbol (re-find #"(?<=\$).*(?=@)"
                       (str x)))
=> foo

此外,整个过程可以推广到一个函数,该函数将接受一个函数并返回与该函数的var名称相关联的符号 - 这是在最初定义函数时给出的符号(此过程根本不会很好地为匿名函数工作)。

(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功能真的不太好。当命名空间中的多个内容解析为相同的值时,会发生问题。你可以返回这个解析为相同内容的符号列表(而不是只返回第一个符号),然后从那里进一步处理它。更改当前函数以使其工作非常简单:删除最后两行(firstkey),并将其替换为(map key)

无论如何,我希望这对你来说和我一样有趣和有趣,但我怀疑这些黑客中的任何一个是否都是好的处理事情的方式。我主张我的第一个解决方案。

答案 1 :(得分:2)

目前尚不清楚为什么要这样做 - 当您执行(def x foo)时,您实际上将名称x提供给命名空间中的新var。它碰巧具有与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