檢視原始碼 引號和取消引號
本指南旨在介紹 Elixir 中可用的元程式設計技巧。透過其自身資料結構表示 Elixir 程式,是元程式設計的核心。本章節從探討這些結構和相關的 quote/2
和 unquote/1
建構開始,以便我們在下一指南中檢視巨集,最後建立我們自己的特定領域語言。
引號
Elixir 程式的建構區塊是包含三個元素的元組。例如,函式呼叫 sum(1, 2, 3)
在內部表示為
{:sum, [], [1, 2, 3]}
你可以透過使用 quote/2
巨集取得任何表達式的表示
iex> quote do: sum(1, 2, 3)
{:sum, [], [1, 2, 3]}
第一個元素是函式名稱,第二個是包含元資料的關鍵字清單,第三個是引數清單。
運算子也表示為此類元組
iex> quote do: 1 + 2
{:+, [context: Elixir, import: Kernel], [1, 2]}
甚至地圖也表示為對 %{}
的呼叫
iex> quote do: %{1 => 2}
{:%{}, [], [{1, 2}]}
變數使用此類三重項表示,不同之處在於最後一個元素是原子,而不是清單
iex> quote do: x
{:x, [], Elixir}
在引號更複雜的表達式時,我們可以看到程式以此類元組表示,這些元組通常在類似樹狀結構中彼此巢狀。許多語言會將此類表示稱為 抽象語法樹 (AST)。Elixir 稱它們為引號表達式
iex> quote do: sum(1, 2 + 3, 4)
{:sum, [], [1, {:+, [context: Elixir, import: Kernel], [2, 3]}, 4]}
有時,在使用引號表達式時,取得文字程式表示可能會很有用。這可以使用 Macro.to_string/1
來完成
iex> Macro.to_string(quote do: sum(1, 2 + 3, 4))
"sum(1, 2 + 3, 4)"
一般而言,上述元組會根據下列格式進行結構化
{atom | tuple, list, list | atom}
- 第一個元素是原子或其他採用相同表示的元組;
- 第二個元素是包含元資料的關鍵字清單,例如數字和內容;
- 第三個元素可以是函數呼叫參數清單或原子。當此元素為原子時,表示元組代表變數。
除了上述定義的元組外,還有五個 Elixir 文字,在引用時會回傳它們自己(而不是元組)。它們是
:sum #=> Atoms
1.0 #=> Numbers
[1, 2] #=> Lists
"strings" #=> Strings
{key, value} #=> Tuples with two elements
大多數 Elixir 程式碼都可以直接轉換成其底層引用表達式。我們建議您嘗試不同的程式碼範例,看看結果如何。例如,String.upcase("foo")
會擴充成什麼?我們也學到 if(true, do: :this, else: :that)
等於 if true do :this else :that end
。此肯定句在引用表達式中如何成立?
取消引用
引用是關於擷取特定程式碼區塊的內部表示法。然而,有時可能需要在我們想要擷取的表示法中注入其他特定程式碼區塊。
例如,假設您有一個名為 number
的變數,其中包含您想要注入引用表達式中的數字。
iex> number = 13
iex> Macro.to_string(quote do: 11 + number)
"11 + number"
這不是我們想要的,因為 number
變數的值尚未注入,而且 number
已在表達式中被引用。為了注入 number
變數的值,必須在引用表示法中使用 unquote/1
iex> number = 13
iex> Macro.to_string(quote do: 11 + unquote(number))
"11 + 13"
unquote/1
甚至可以用來注入函數名稱
iex> fun = :hello
iex> Macro.to_string(quote do: unquote(fun)(:world))
"hello(:world)"
在某些情況下,可能需要在清單中注入多個值。例如,假設您有一個包含 [1, 2, 6]
的清單,我們想要注入 [3, 4, 5]
。使用 unquote/1
無法產生預期的結果
iex> inner = [3, 4, 5]
iex> Macro.to_string(quote do: [1, 2, unquote(inner), 6])
"[1, 2, [3, 4, 5], 6]"
此時,unquote_splicing/1
就派上用場了
iex> inner = [3, 4, 5]
iex> Macro.to_string(quote do: [1, 2, unquote_splicing(inner), 6])
"[1, 2, 3, 4, 5, 6]"
在使用巨集時,取消引用非常有用。在撰寫巨集時,開發人員可以接收程式碼區塊,並將它們注入其他程式碼區塊,可用於轉換程式碼或撰寫在編譯期間產生程式碼的程式碼。
跳脫
正如我們在本章節一開始所看到的,只有某些值是 Elixir 中有效的引用表達式。例如,映射不是有效的引用表達式。四個元素的元組也不是。然而,這些值可以表示為引用表達式
iex> quote do: %{1 => 2}
{:%{}, [], [{1, 2}]}
在某些情況下,您可能需要將這些值注入引用表達式中。為此,我們需要先使用 Macro.escape/1
將這些值跳脫為引用表達式
iex> map = %{hello: :world}
iex> Macro.escape(map)
{:%{}, [], [hello: :world]}
巨集接收引號表達式,且必須傳回引號表達式。然而,有時在巨集執行期間,您可能需要使用值,且必須區分值與引號表達式。
換句話說,區分一般 Elixir 值(例如清單、映射、程序、參考等)與引號表達式非常重要。某些值,例如整數、原子和字串,其引號表達式等於值本身。其他值,例如映射,則需要明確轉換。最後,函數和參考等值根本無法轉換為引號表達式。
使用巨集和產生程式碼的程式碼時,請查看 Macro
模組的說明文件,其中包含許多用於處理 Elixir AST 的函數。
在本簡介中,我們已奠定基礎,終於可以撰寫我們的首個巨集。您可以在 下一指南 中查看。