此 EEP 提議兩個新的巨集,分別稱為 FUNCTION_NAME
和 FUNCTION_ARITY
,它們將分別回傳目前函式的名稱和參數個數。
新的預定義巨集 FUNCTION_NAME
會展開為目前函式的名稱(以 atom 的形式)。新的預定義巨集 FUNCTION_ARITY
會展開為目前函式的參數個數(以整數的形式)。範例:
a_function(_, _) ->
{?FUNCTION_NAME,?FUNCTION_ARITY}.
經過預處理後,範例會變成如下:
a_function(_, _) ->
{a_function,2}.
預處理器會在展開 FUNCTION_NAME
和 FUNCTION_ARITY
巨集之前,展開所有其他巨集。因此,如果我們有這個範例:
-define(F, a_function).
?F(_, _) ->
{?FUNCTION_NAME,?FUNCTION_ARITY}.
預處理器會先將它展開為:
a_function(_, _) ->
{?FUNCTION_NAME,?FUNCTION_ARITY}.
然後再展開為:
a_function(_, _) ->
{a_function,2}.
FUNCTION_NAME
和 FUNCTION_ARITY
巨集可以用於任何以 atom 開頭,後接左括號的形式(當所有其他巨集都已展開時)。這些巨集甚至可以用於函式頭。因此,以下範例是合法的(雖然不是很有用):
a(?FUNCTION_NAME) -> ok.
它將展開為:
a(a) -> ok.
FUNCTION_NAME
和 FUNCTION_ARITY
巨集在函式頭中存在其他巨集的情況下也會起作用。範例:
-define(__, _, _).
b(?FUNCTION_NAME, ?FUNCTION_ARITY, ?__) ->
ok.
此程式碼會先展開為:
b(?FUNCTION_NAME, ?FUNCTION_ARITY, _, _) ->
ok.
然後再展開為:
b(b, 4, _, _) ->
ok.
在屬性中使用 FUNCTION_NAME
或 FUNCTION_ARITY
會導致編譯錯誤。範例:
-attr(?FUNCTION_NAME).
錯誤訊息會如下所示:
example.erl:4: ?FUNCTION_NAME can only be used within a function
FUNCTION_NAME
或 FUNCTION_ARITY
的調用不得作為一個形式的開始。因此,以下範例是不合法的:
?FUNCTION_NAME() -> ok.
錯誤訊息會如下所示:
example.erl:4: ?FUNCTION_NAME must not begin a form
此 EEP 沒有明確指定 FUNCTION_NAME
和 FUNCTION_ARITY
巨集應該如何實作,但確實對實作施加了一些要求:
實作必須有效率。特別是,對於不使用 FUNCTION_NAME
或 FUNCTION_ARITY
巨集的模組,不應該有任何明顯的效能下降。
FUNCTION_NAME
和 FUNCTION_ARITY
的展開必須由 epp
模組完成。將巨集的展開延遲到稍後的編譯器傳遞是不可接受的,因為這可能會導致與在抽象格式上運作的 parse transforms 和其他工具的相容性問題。
-define(FUNCTION_STRING, atom_to_list(?FUNCTION_NAME) ++ "/" ++
integer_to_list(?FUNCTION_ARITY)).
test() ->
?FUNCTION_STRING.
test/0
函式將回傳 "test/0"
。請注意,BEAM 編譯器會在編譯時評估常數運算式;因此,FUNCTION_STRING
將在編譯期間轉換為字串字面值。
c() ->
F = fun() -> ?FUNCTION_NAME end,
F().
c/0
函式將回傳 c
。
在建立參照包含函式的 fun 時,可以使用巨集:
self_ref(Data, Handler) ->
...
Handler(Data, fun ?FUNCTION_NAME/?FUNCTION_ARITY)
...
許多使用者都要求某種可以回傳目前函式名稱的巨集,類似於 FILE
、LINE
和 MODULE
。例如:為什麼沒有 ?FUNCTION 巨集。
函式名稱巨集最常見的用例似乎是將資訊記錄到日誌檔案中。可能的解決方案包括使用 parse transform、使用 process_info/2
或產生並捕獲例外。除非應用程式因為其他原因需要 parse transform,否則僅為了捕獲目前函式的名稱而實作 parse transform 是很麻煩的。其他解決方案則有執行時效能上的損失。
FUNCTION
巨集? #為了盡量減少預處理器符號命名空間的污染,難道不應該只有一個單一的 FUNCTION
巨集,它可以回傳一個包含目前函式的名稱和參數個數的 tuple 嗎?
這當然是可行的,但許多常見的用例會有點麻煩:
io:format("~p/~p: ~s\n", [element(1, ?FUNCTION),
element(2, ?FUNCTION),
Message])
將其與更易讀的內容進行比較:
io:format("~p/~p: ~s\n", [?FUNCTION_NAME,
?FUNCTION_ARITY,
Message])
在某些情況下,element(1, ?FUNCTION)
或 element(2, ?FUNCTION)
也會是非法的,例如在函式頭中或 fun
關鍵字之後。以下範例將無法編譯:
fun element(1, ?FUNCTION)/element(2, ?FUNCTION)
FUNCTION_STRING
? #最重要的原因是存在兩個合理的定義:
-define(FUNCTION_STRING,
atom_to_list(?FUNCTION_NAME) ++ "/" ++
integer_to_list(?FUNCTION_ARITY)).
和:
-define(FUNCTION_STRING,
?MODULE_STRING ++ ":" ++
atom_to_list(?FUNCTION_NAME) ++ "/" ++
integer_to_list(?FUNCTION_ARITY)).
自己定義 FUNCTION_STRING
沒有執行時效能上的損失,因為編譯器會在編譯期間將 FUNCTION_STRING
的任何一種定義轉換為字面字串。
另一個原因是避免用比嚴格需要的更多的預定義巨集污染巨集命名空間。
歷史記錄:MODULE_STRING
是在 OTP R7B 中作為最佳化新增的,因為當時編譯器無法像現在這樣最佳化常數運算式。
FUNCTION_NAME
和 FUNCTION_ARITY
? #我看不到在函式頭中使用 FUNCTION_NAME
和 FUNCTION_ARITY
巨集的任何實際用途。只允許在函式主體中使用它們似乎比較合理。但是請考慮以下範例:
f(a, _) ->
ok;
f(?FUNCTION_NAME, ?FUNCTION_ARITY) ->
ok.
為了能夠拒絕在第一個子句以外的其他子句中調用 FUNCTION_NAME
和 FUNCTION_ARITY
,預處理器基本上必須能夠解析任意 Erlang 程式碼。唯一可行的解決方案是使用 erl_parse
模組中的現有解析器。這會在沒有提供任何額外好處的情況下減慢預處理器的速度。
定義了 FUNCTION_NAME
或 FUNCTION_ARITY
的模組將無法編譯,並顯示類似於以下的訊息:
example.erl:4: redefining predefined macro 'FUNCTION_NAME'
同樣,嘗試使用 -D
從命令列定義 FUNCTION_NAME
或 FUNCTION_ARITY
也會失敗。
參考實作對於不使用 FUNCTION_NAME
或 FUNCTION_ARITY
巨集的函式,基本上沒有額外的成本。只有在看到 FUNCTION_NAME
或 FUNCTION_ARITY
的調用時,預處理器才會開始掃描以確定目前函式的名稱和參數個數。掃描將在找到參數列表末尾的右括號時停止。如果看到在同一個函式定義中再次調用 FUNCTION_NAME
或 FUNCTION_ARITY
,則會儲存並重複使用名稱和參數個數。
參考實作可以從 Github 上取得,如下所示:
git fetch git://github.com/bjorng/otp.git bjorn/function-macro
本文檔已放置在公有領域。