3

xをシンボル関数fooとして定義するとします。

(defn foo [x] x)

(def x foo)

xだけを指定すると、「foo」という名前を見つけることができますか?

関数xの名前(この場合は「foo」)を検索する方法はfoo内にありますか?

(foo x)

次のような関数を作成することはできますか、または可能ですか。

(get-fn-name x)
foo
4

2 に答える 2

9

最近、このサイトで同様の質問がありました。ここを参照してください

を実行すると(def x foo)、xを「それ自体」fooではなく「の値」として定義することになります。fooその値に解決されるとfoo、その値は、とはまったく関係がなくなりますfoo

ですから、あなたは今あなたの質問に対する一つの可能​​な答えを見るかもしれません:fooあなたがdefineをするために行くとき解決しないでくださいx。する代わりに...

(def x foo)

...行う...

(def x 'foo)

ここで、の値を取得しようとすると、解決される値ではなく、(文字通り)x取得されます。foofoo

user> x
=> foo

ただし、これは問題になる可能性があります。これfooは、を使用することで解決される値を取得できるようにしたい場合もあるためですx。ただし、次のようにすることでこれを行うことができます。

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

これが何であるかを説明すると、「値を取得し、それをシンボルとして使用し、次にそのシンボルをそのvar(値ではなくx)に解決し、そのvarを逆参照して値を取得します」。

...それでは、何かハッキーなことをしましょう。私が提案しようとしているこれらのことのいずれかを行うことをお勧めするかどうかはわかりませんが、あなたは尋ねましたCan the name "foo" be discovered if only given x?、そして私はあなたがそれをすることができる2つの方法を考えることができます。

方法1:fn varnameを正規表現します。以下に解決する内容と両方に
注意してください。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名が、最初に定義するために使用されたシンボルへの後方参照を使用して作成されているように見えるためです。この事実を使用して、任意の関数からそのシンボルを見つけます。

そこで、関数varnameの文字列内に「foo」を見つけるための正規表現を作成しました。「foo」を検索するのではなく、正規表現の用語で文字が前にあり、その後に文字が続くサブ文字列を検索".*"します。正規表現で..。\$"(?<=\$)"\@"(?=@)"

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

これをさらにラップするだけでシンボルに変換できます(symbol ...)

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

さらに、このプロセス全体は、関数を受け取り、その関数の変数名に関連付けられたシンボルを返す関数に一般化できます。これは、関数が最初に定義されたときに指定されたシンボルです(このプロセスは、匿名関数)。

(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:IDに基づいてすべてのnsマッピングを逆引き参照する
これは楽しいことです。

したがって、すべての名前空間マッピングを取得し、そこから、各マッピングのvalが解決対象とまったく同じものdissoc 'x参照しているかどうかに基づいて、残っているものをフィルタリングします。そのフィルタリングされたシーケンスの最初のものを取得し、次にシンボルを取得するためにその最初のものでキーを取得します。x

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

上記に置き換えるxと、が得られることに注意してください。実際には、これが行っているのは、とまったく同じ値にマップされていることがわかった名を返すことだけです。前と同じように、これは関数に一般化できます。fooxx

(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機能は本当に良くありません。名前空間に複数のものがあり、同じ値に解決されると、問題が発生します。(最初のシンボルを返すだけでなく)同じものに解決されるこのシンボルのリストを返し、そこからさらに処理することができます現在の関数を変更してこれを機能させるのは簡単です。最後の2行(firstおよびkey)を削除し、それらを。に置き換えます(map key)

とにかく、これが私と同じようにあなたにとって楽しくて面白いことを願っていますが、これらのハックのどちらかが物事を進める良い方法になるかどうかは疑問です。私は私の最初の解決策を提唱します。

于 2012-08-11T02:52:23.263 に答える
2

なぜこれを実行するのかは明確ではありません。実行すると、名前空間の新しい変数に(def x foo)効果的に名前を付けることになります。xたまたま同じ値を持っていますfoo(つまり、同じ関数が含まれています)が、それ以外はfooから完全に独立しています。これは、Javaのアナロジーを使用するために、同じオブジェクトへの2つの参照を持つようなものです。

なぜあなたは名前を取得し続けたいの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
于 2012-08-11T01:23:36.783 に答える