假设我将x定义为符号函数foo
(defn foo [x] x)
(def x foo)
如果仅给出x?
,是否可以发现名称“foo”在foo中是否有办法查找函数x的名称 - 在这种情况下为“foo”?
(foo x)
是否可以创建如下的函数:
(get-fn-name x)
foo
答案 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名称
请注意foo
和x
都解析为以下内容:
(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
功能真的不太好。当命名空间中的多个内容解析为相同的值时,会发生问题。你可以返回这个解析为相同内容的符号列表(而不是只返回第一个符号),然后从那里进一步处理它。更改当前函数以使其工作非常简单:删除最后两行(first
和key
),并将其替换为(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