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
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.
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