簡體   English   中英

如何從Clojure中的符號獲取函數名稱?

[英]How can I get the name of a function from a symbol in clojure?

假設我將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有任何關系。

因此,也許您現在看到了一個可能的問題答案:在定義x時不要解析foo 而不是做...

(def x foo)

...做...

(def x 'foo)

現在,如果您嘗試獲取x的值,則將得到foo (從字面上看),而不是foo解析為的值。

user> x
=> foo

但是,這可能有問題,因為您有時可能還希望能夠獲得foo解析為使用x的值。 但是,您可以通過執行以下操作來做到這一點:

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

如果我要描述它的作用:“獲取x解析的值,將其用作符號,然后將該符號解析為其var( 而不是其值),然后取消引用該var以獲得值”。

...現在讓我們做些駭人聽聞的事情。 我不確定我會建議我做建議做的這兩種事情,但是您確實問Can the name "foo" be discovered if only given x? ,我可以想到兩種方法。

方法#1:正則表達式fn var名稱
請注意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 ,因為foo解析為一個函數,該函數恰好具有類似var的名稱,該名稱對於x將是相同的,因為它引用了相同的函數。 注意,“ foo”包含在(str x) (以及(foo x) )產生的字符串中。 這是因為函數的var名稱顯然是通過向后引用最初用於定義它的符號創建的。 我們將利用這一事實從任何函數中找到該符號。

因此,我編寫了一個正則表達式以在函數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函數確實不是很好。 當名稱空間中有多個事物解析為相同的值時,就會發生問題。 可以返回解析為相同事物的符號列表(而不只是返回第一個符號),然后從那里進行進一步處理。 更改當前函數以使其起作用很簡單:刪除最后兩行( firstkey ),並用(map key)替換它們。

無論如何,我希望這對您和我都一樣有趣和有趣,但是我懷疑這些黑客中的任何一個是否都是解決問題的方法。 我主張我的第一個解決方案。

目前尚不清楚為什么要這樣做-當您這樣做(def x foo)時,實際上是在名稱空間中為新的var賦予名稱x 它恰好具有與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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM