elixir官方教程 元編程(一) 引用與去引用

#引用與去引用api

  1. 引用(Quoting)
  2. 去引用(Unquoting)
  3. 釋放(Escaping)

一個Elixir程序能夠用它本身的數據結構來表現.本章,咱們將會學習這些結構體的特色和如何組成它們.本章咱們要學習的概念是爲宏的積木(building blocks),在下一章中咱們將深刻研究它.數據結構

#引用(Quoting)函數

Elixir程序中的積木是一個三元素元組.例如,函數sum(1, 2, 3)的內部表示是:學習

{:sum, [], [1, 2, 3]}

你能夠用quote宏來獲得任何表達式的內部表現:ui

iex> quote do: sum(1, 2, 3)
{:sum, [], [1, 2, 3]}

第一個元素是函數名,第二個元素是一個包含了元數據的關鍵詞列表,第三個元素是參數列表.this

操做符也能夠用元組來表示:atom

iex> quote do: 1 + 2
{:+, [context: Elixir, import: Kernel], [1, 2]}

甚至一個映射都被表示成對%{}的調用:code

iex> quote do: %{1 => 2}
{:%{}, [], [{1, 2}]}

變量也能這樣三段式表示,只不過最後的元素換成了一個原子:orm

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.它要如何用引用表達式來容納?

#去引用(Unquoting)

引用能夠得到一些特定代碼塊的內部表達式.然而,有時咱們須要注入一些特定代碼塊到咱們想要獲取的表達式中.

例如,假設咱們有一個變量,包含了咱們想注入一個引用表達式中的數字,變量名爲number.

iex> number = 13
iex> Macro.to_string(quote do: 11 + number)
"11 + number"

那不是咱們想要的,number變量被引入了表達式,但number變量的值沒有被注入.爲了注入number變量的值,咱們須要在引用表達式中使用unquote:

iex> number = 13
iex> Macro.to_string(quote do: 11 + unquote(number))
"11 + 13"

unquote甚至能夠被用於注入函數名:

iex> fun = :hello
iex> Macro.to_string(quote do: unquote(fun)(:world))
"hello(:world)"

在相同的狀況下,可能須要注入一個有許多值的列表.好比,假設你有一個列表[1, 2, 6],咱們想把[3, 4, 5]注入進去.使用unquote卻不能獲得想要的結果:

iex> inner = [3, 4, 5]
iex> Macro.to_string(quote do: [1, 2, unquote(inner), 6])
"[1, 2, [3, 4, 5], 6]"

這時就輪到unquote_splicing出場了:

iex> inner = [3, 4, 5]
iex> Macro.to_string(quote do: [1, 2, unquote_splicing(inner), 6])
"[1, 2, 3, 4, 5, 6]"

去引用在操做宏的時候頗有用.編寫宏的時候,開發者能夠獲取代碼塊並將它們注入到其它代碼塊中,這能夠被用於改變代碼或者編寫能在編譯時生成代碼的代碼.

#釋放(Escaping)

如本章開頭所見,Elixir中只有一部分值有合法的引用表達式.好比,一個映射沒有合法的引用表達式.四元素的元組也沒有.然而,這些值能夠被表示成一個引用表達式:

iex> quote do: %{1 => 2}
{:%{}, [], [{1, 2}]}

有時你須要將這種值注入引用表達式中.咱們首先須要將這些值釋放到引用表達式中,使用Macro.escape/1來完成:

iex> map = %{hello: :world}
iex> Macro.escape(map)
{:%{}, [], [hello: :world]}

宏接收引用表達式,並必須返回引用表達式.然而執行宏的過程當中,你可能須要處理一些值,而且區分值與引用表達式.

也就是說,區分常規的Elixir值(例如列表,映射,進程,參考等等)與引用表達式,是很重要的.有一些值的引用表達式就是它們本身,例如整數,原子覈字符串.另外一些值,好比映射,須要被顯式轉換.最後,函數與參考徹底不能被轉化成引用表達式.

你能夠在Kernel.SpecialForms模塊中閱讀更多關於quoteunquote的內容.在Macro模塊中能夠找到Macro.escape/1的文檔和其它與引用表達式相關的函數.

在本教程中咱們將編寫本身的第一個宏,讓咱們進入下一章吧.

相關文章
相關標籤/搜索