How can I get the name of a function from a symbol

2019-07-01 15:10发布

Suppose I define x as symbol function foo

(defn foo [x] x)

(def x foo)

Can the name "foo" be discovered if only given x?

Is there a way within foo to look up the name of the function x - "foo" in this case?

(foo x)

Is there or is it possible to create a function such as:

(get-fn-name x)
foo

2条回答
Melony?
2楼-- · 2019-07-01 15:44

A similar question was asked recently on this site; see here

When you do (def x foo), you are defining x to be "the value at foo", not "foo itself". Once foo has resolved to its value, that value no longer has any relationship whatsoever to foo.

So maybe you see one possible answer to your question now: don't resolve foo when you go to do define x. Instead of doing...

(def x foo)

...do...

(def x 'foo)

Now if you try to get the value of x, you will get foo (literally), not the value that foo resolves to.

user> x
=> foo

However, that is likely problematic, because you will probably also sometimes want to be able to get at the value that foo resolves to using x. However however, you would be able to do this by doing:

user> @(resolve x)
=> #<user$foo user$foo@157b46f>

If I were to describe what this does it would be: "get the value x resolves to, use that as a symbol, then resolve that symbol to its var (not its value), and dereference that var to obtain a value".

...Now let's do something hacky. I'm not sure I would advise doing either of these things I'm about to suggest, but you did ask Can the name "foo" be discovered if only given x?, and I can think of two ways you could do that.

Method #1: regex the fn var name
Notice what foo and x both resolve to below:

(defn foo [a] (println a))
(def x foo)

user> foo
=> #<user$foo user$foo@1e2afb2>
user> x
=> #<user$foo user$foo@1e2afb2>

Now, check this out:

user> (str foo)
=> "user$foo@1e2afb2"
user> (str x)
=> "user$foo@1e2afb2"

Cool. This only works because foo resolves to a function, which happens to have a var-like name, a name which will be the same for x because it refers to the same function. Note that "foo" is contained within the string produced by (str x) (and also by (foo x)). This is because the function's var name is apparently created with some backwards reference to the symbol that was used to initially define it. We're going to use this fact to find that very symbol from any function.

So, I wrote a regular expression to find "foo" inside that string of the function var name. It isn't that it looks for "foo", but rather that it looks for any sub-string--in regex terms, ".*"--that is preceded by a \$ character--in regex terms "(?<=\$)"--and followed by the \@ character--in regex terms "(?=@)"...

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

We can further convert this to a symbol by simply wrapping (symbol ...) around it:

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

Furthermore, this whole process could be generalized to a function that will take a function and return the symbol associated with that function's var name--which is the symbol was given when the function was initially defined (this process will not at all work nicely for anonymous functions).

(defn get-fn-init-sym [f]
  (symbol (re-find #"(?<=\$).*(?=@)" (str f))))

...or this which I find nicer to read...

(defn get-fn-init-sym [f]
  (->> (str f)
       (re-find #"(?<=\$).*(?=@)")
       symbol))

Now we can do...

user> (get-fn-init-sym x)
=> foo

Method #2: reverse lookup all ns mappings based on identity
This is going to be fun.

So, we're going to take all the namespace mappings, then dissoc 'x from it, then filter what remains based on whether the val at each mapping refers to the exact same thing as what x resolves to. We'll take the first thing in that filtered sequence, and then we'll take the key at that first thing in order to get the symbol.

user> (->> (dissoc (ns-map *ns*) 'x)
           (filter #(identical? (let [v (val %)]
                                  (if (var? v) @v v))
                                x))
           first
           key)
=> foo

Notice that if you replaced x with foo above, you would get x. Really all this is doing is returning the first name it finds that maps to the exact same value as x. As before, this could be generalized to a function:

(defn find-equiv-sym [sym]
  (->> (dissoc (ns-map *ns*) sym)
       (filter #(identical? (let [v (val %)]
                              (if (var? v) @v v))
                            @(resolve sym)))
       first
       key))

The main difference here is that the argument will have to be a quoted symbol.

user> (find-equiv-sym 'x)
=> foo

This find-equiv-sym function is really not very good. Problems will happen when you have multiple things in the namespace resolving to identical values. You could return this list of symbols that resolve to identical things (instead of just returning the first one), and then process it further from there. It would be simple to change the current function to make this work: delete the last two lines (first and key), and replace them with (map key).

Anyways, I hope this was as fun and interesting for you as it was for me, but I doubt whether either of these hacks would be a good way of going about things. I advocate my first solution.

查看更多
趁早两清
3楼-- · 2019-07-01 16:01

It's not clear why you would want to do this - when you do (def x foo) you are effectively giving the name x to a new var in your namespace. It happens to have the same value as foo (i.e. it contains the same function) but is otherwise completely independent from foo. It's like having two references to the same object, to use a Java analogy.

Why should you continue to want to obtain the name foo?

If you really want to do something similar to this, this might be a case where you could use some custom metadata on the function which contains the original symbol:

(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
查看更多
登录 后发表回答