作者
Björn Gustavsson <bjorn(at)erlang(dot)org>
狀態
已接受/19.0 在 OTP 19 版本中實作
類型
標準追蹤
建立
2015-10-27
Erlang 版本
OTP-19.0
發布歷史
2015-10-29, 2015-11-09, 2015-11-11, 2015-11-16

EEP 45:用於函式名稱和參數個數的新巨集 #

摘要 #

此 EEP 提議兩個新的巨集,分別稱為 FUNCTION_NAMEFUNCTION_ARITY,它們將分別回傳目前函式的名稱和參數個數。

規格 #

新的預定義巨集 FUNCTION_NAME 會展開為目前函式的名稱(以 atom 的形式)。新的預定義巨集 FUNCTION_ARITY 會展開為目前函式的參數個數(以整數的形式)。範例:

a_function(_, _) ->
  {?FUNCTION_NAME,?FUNCTION_ARITY}.

經過預處理後,範例會變成如下:

a_function(_, _) ->
  {a_function,2}.

預處理器會在展開 FUNCTION_NAMEFUNCTION_ARITY 巨集之前,展開所有其他巨集。因此,如果我們有這個範例:

-define(F, a_function).
?F(_, _) ->
  {?FUNCTION_NAME,?FUNCTION_ARITY}.

預處理器會先將它展開為:

a_function(_, _) ->
  {?FUNCTION_NAME,?FUNCTION_ARITY}.

然後再展開為:

a_function(_, _) ->
  {a_function,2}.

FUNCTION_NAMEFUNCTION_ARITY 巨集可以用於任何以 atom 開頭,後接左括號的形式(當所有其他巨集都已展開時)。這些巨集甚至可以用於函式頭。因此,以下範例是合法的(雖然不是很有用):

a(?FUNCTION_NAME) -> ok.

它將展開為:

a(a) -> ok.

FUNCTION_NAMEFUNCTION_ARITY 巨集在函式頭中存在其他巨集的情況下也會起作用。範例:

-define(__, _, _).
b(?FUNCTION_NAME, ?FUNCTION_ARITY, ?__) ->
   ok.

此程式碼會先展開為:

b(?FUNCTION_NAME, ?FUNCTION_ARITY, _, _) ->
   ok.

然後再展開為:

b(b, 4, _, _) ->
  ok.

在屬性中使用 FUNCTION_NAMEFUNCTION_ARITY 會導致編譯錯誤。範例:

-attr(?FUNCTION_NAME).

錯誤訊息會如下所示:

example.erl:4: ?FUNCTION_NAME can only be used within a function

FUNCTION_NAMEFUNCTION_ARITY 的調用不得作為一個形式的開始。因此,以下範例是不合法的:

?FUNCTION_NAME() -> ok.

錯誤訊息會如下所示:

example.erl:4: ?FUNCTION_NAME must not begin a form

實作要求 #

此 EEP 沒有明確指定 FUNCTION_NAMEFUNCTION_ARITY 巨集應該如何實作,但確實對實作施加了一些要求:

  • 實作必須有效率。特別是,對於不使用 FUNCTION_NAMEFUNCTION_ARITY 巨集的模組,不應該有任何明顯的效能下降。

  • FUNCTION_NAMEFUNCTION_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)
    ...

動機 #

許多使用者都要求某種可以回傳目前函式名稱的巨集,類似於 FILELINEMODULE。例如:為什麼沒有 ?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_NAMEFUNCTION_ARITY#

我看不到在函式頭中使用 FUNCTION_NAMEFUNCTION_ARITY 巨集的任何實際用途。只允許在函式主體中使用它們似乎比較合理。但是請考慮以下範例:

f(a, _) ->
  ok;
f(?FUNCTION_NAME, ?FUNCTION_ARITY) ->
  ok.

為了能夠拒絕在第一個子句以外的其他子句中調用 FUNCTION_NAMEFUNCTION_ARITY,預處理器基本上必須能夠解析任意 Erlang 程式碼。唯一可行的解決方案是使用 erl_parse 模組中的現有解析器。這會在沒有提供任何額外好處的情況下減慢預處理器的速度。

回溯相容性 #

定義了 FUNCTION_NAMEFUNCTION_ARITY 的模組將無法編譯,並顯示類似於以下的訊息:

example.erl:4: redefining predefined macro 'FUNCTION_NAME'

同樣,嘗試使用 -D 從命令列定義 FUNCTION_NAMEFUNCTION_ARITY 也會失敗。

實作 #

參考實作對於不使用 FUNCTION_NAMEFUNCTION_ARITY 巨集的函式,基本上沒有額外的成本。只有在看到 FUNCTION_NAMEFUNCTION_ARITY 的調用時,預處理器才會開始掃描以確定目前函式的名稱和參數個數。掃描將在找到參數列表末尾的右括號時停止。如果看到在同一個函式定義中再次調用 FUNCTION_NAMEFUNCTION_ARITY,則會儲存並重複使用名稱和參數個數。

參考實作可以從 Github 上取得,如下所示:

git fetch git://github.com/bjorng/otp.git bjorn/function-macro

版權 #

本文檔已放置在公有領域。