假设我将 x 定义为符号函数 foo
(defn foo [x] x)
(def x foo)
如果仅给定 x,是否可以发现名称“foo”?
在这种情况下, foo 中有没有办法查找函数 x - "foo" 的名称?
(foo x)
是否有或者是否可以创建一个函数,例如:
(get-fn-name x)
foo
假设我将 x 定义为符号函数 foo
(defn foo [x] x)
(def x foo)
如果仅给定 x,是否可以发现名称“foo”?
在这种情况下, foo 中有没有办法查找函数 x - "foo" 的名称?
(foo x)
是否有或者是否可以创建一个函数,例如:
(get-fn-name x)
foo
最近在这个网站上提出了一个类似的问题;看这里
当您这样做时(def x foo)
,您将 x 定义为“值foo
”,而不是“foo
本身”。一旦foo
解析为它的值,该值就不再与 有任何关系foo
。
因此,也许您现在看到了对您的问题的一个可能的答案:foo
当您执行 define 时不要解决x
。而不是做...
(def x foo)
...做...
(def x 'foo)
现在,如果您尝试获取 的值x
,您将获得foo
(字面意思),而不是foo
解析为的值。
user> x
=> foo
但是,这可能是有问题的,因为您有时可能还希望能够获得foo
解析为 using的值x
。但是,您可以通过以下方式执行此操作:
user> @(resolve x)
=> #<user$foo user$foo@157b46f>
如果我要描述它的作用,那将是:“获取x
解析为的值,将其用作符号,然后将该符号解析为其 var(而不是其值),并取消引用该 var 以获取值”。
...现在让我们做一些 hacky。我不确定我是否会建议做我将要建议的这些事情,但你确实问过Can the name "foo" be discovered if only given x?
,我可以想到两种方法可以做到这一点。
方法 #1:正则表达式 fn var name
注意下面的内容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 名称显然是通过对最初定义它的符号的一些向后引用创建的。我们将使用这个事实从任何函数中找到那个符号。
因此,我编写了一个正则表达式来在函数 var 名称的字符串中查找“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)
.
无论如何,我希望这对你来说和对我一样有趣和有趣,但我怀疑这些技巧中的任何一个是否都是处理事情的好方法。我提倡我的第一个解决方案。
目前尚不清楚您为什么要这样做 - 当您这样做时,(def x foo)
您实际上是在将名称赋予名称x
空间中的新 var。它恰好具有与 foo 相同的值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