檢視原始碼 語法參考

Elixir 語法設計為可直接轉換成抽象語法樹 (AST)。這表示 Elixir 語法大多是統一的,僅有少數「語法糖」結構可減少常見 Elixir 慣用語法中的雜訊。

本文件涵蓋所有 Elixir 語法結構作為參考,並討論其確切的 AST 表示。

保留字

以下是 Elixir 語言中的保留字。它們在整份指南中都有詳細說明,但在此處簡要說明以方便閱讀

  • truefalsenil - 用作原子
  • whenandornotin - 用作運算子
  • fn - 用於匿名函式定義
  • doendcatchrescueafterelse - 用於 do-end 區塊

資料類型

數字

Elixir 中的整數 (1234) 和浮點數 (123.4) 表示為一連串數字,這些數字可用底線分隔以提高可讀性,例如 1_000_000。整數在表示中從不包含小數點 (.)。浮點數包含小數點,且小數點後至少有一個其他數字。浮點數也支援科學記號,例如 123.4e10123.4E10

原子

未加引號的原子以冒號 (:) 開頭,冒號後必須緊接 Unicode 字母或底線。原子可以繼續使用 Unicode 字母、數字、底線和 @ 的序列。原子可以用 !? 結尾。有效的未加引號原子為::ok:ISO8601:integer?

如果冒號後緊接一對雙引號或單引號,並將原子名稱括起來,則該原子視為已加引號。與未加引號的原子不同,此原子可以由任何 Unicode 字元組成(不只字母),例如 :'🌢 Elixir':"++olá++":"123"

具有相同名稱的加引號和未加引號原子被視為等效,因此 :atom:"atom":'atom' 表示相同的原子。唯一的缺點是,當在不需要加引號的原子中使用引號時,編譯器會發出警告。

Elixir 中的所有運算子都是有效的原子。有效的範例為 :foo:FOO:foo_42:foo@bar:++。無效的範例為 :@foo(開頭不允許 @)、:123(開頭不允許數字)和 :(*)(不是有效的運算子)。

truefalsenil 是保留字,分別由原子 :true:false:nil 表示。

如需深入了解原子中允許的所有 Unicode 字元,請參閱 Unicode 語法 文件。

字串

Elixir 中的單行字串寫在雙引號之間,例如 "foo"。字串中的任何雙引號都必須以 \ 進行跳脫。字串支援 Unicode 字元,並儲存為 UTF-8 編碼的二進位資料。

Elixir 中的多行字串寫在三個雙引號之間,並且可以在其中包含未跳脫的引號。產生的字串將以換行符號結尾。最後一個 """ 的縮排用於從內部字串中移除縮排。例如

iex> test = """
...>     this
...>     is
...>     a
...>     test
...> """
"    this\n    is\n    a\n    test\n"
iex> test = """
...>     This
...>     Is
...>     A
...>     Test
...>     """
"This\nIs\nA\nTest\n"

字串在 AST 中總是表示為它們自己。

字元清單

Elixir 中的字元清單寫在單引號中,例如 'foo'。字串中的任何單引號都必須以 \ 進行跳脫。字元清單由非負整數組成,其中每個整數代表一個 Unicode 碼點。

多行字元清單寫在三個單引號 (''') 中,與多行字串相同。

字元清單在 AST 中總是表示為它們自己。

如需更深入的資訊,請閱讀 List 模組中的「字元清單」區段。

清單、元組和二進位資料

資料結構,例如清單、元組和二進位,分別以界定符 [...]{...}<<...>> 標記。每個元素以逗號分隔。尾隨逗號也是允許的,例如 [1, 2, 3,]

映射和關鍵字清單

映射使用 %{...} 符號,每個鍵值對由標記為 => 的成對元素指定,例如 %{"hello" => 1, 2 => "world"}

關鍵字清單(第一個元素為原子的二元組清單)和具有原子鍵的映射都支援關鍵字符號,其中冒號字元 : 移到原子的尾端。 %{hello: "world"} 等於 %{:hello => "world"},且 [foo: :bar] 等於 [{:foo, :bar}]。此符號是一種語法糖,會發出相同的 AST 表示。後續章節將說明此符號。

結構

結構建立在映射語法上,方法是在 %{ 之間傳遞結構名稱。例如,%User{...}

運算式

變數

Elixir 中的變數必須以底線或非大寫或標題格式的 Unicode 字母開頭。變數可以繼續使用 Unicode 字母、數字和底線的序列。變數可以用 ?! 結尾。若要深入了解變數中允許的所有 Unicode 字元,請參閱 Unicode 語法 文件。

Elixir 的命名慣例 建議變數採用 snake_case 格式。

非限定呼叫(區域呼叫)

非限定呼叫,例如 add(1, 2),必須以字元開頭,然後遵循與變數相同的規則,後接括號(可選),然後是引數。

零元呼叫(即沒有引數的呼叫)需要括號,以避免與變數混淆。如果使用括號,它們必須緊接函式名稱,中間沒有空格。例如,add (1, 2) 是語法錯誤,因為 (1, 2) 被視為無效區塊,並嘗試將其作為單一引數傳遞給 add

Elixir 的命名慣例 建議呼叫採用 snake_case 格式。

運算子

與許多程式語言相同,Elixir 也支援運算子,這些運算子會以非限定呼叫的方式,搭配其優先順序和結合性規則。例如 =when&@ 等結構,都會被視為運算子。如需完整參考,請參閱 運算子頁面

限定呼叫(遠端呼叫)

限定呼叫,例如 Math.add(1, 2),必須以字元開頭,然後遵循與變數相同的規則,變數後方可以選擇性地加上括號,然後才是參數。限定呼叫也支援運算子,例如 Kernel.+(1, 2)。Elixir 也允許函式名稱寫在雙引號或單引號之間,這樣一來引號之間的任何字元都允許,例如 Math."++add++"(1, 2)

與非限定呼叫類似,括號對零元函式呼叫(即沒有參數的呼叫)有不同的意義。如果使用括號,例如 mod.fun(),表示函式呼叫。如果省略括號,例如 map.field,表示存取映射的欄位。

Elixir 的命名慣例 建議呼叫採用 snake_case 格式。

別名

別名是在編譯時擴充為原子的結構。別名 String 擴充為原子 :"Elixir.String"。別名必須以 ASCII 大寫字元開頭,後方可以接續任何 ASCII 字母、數字或底線。別名不支援非 ASCII 字元。

多個別名可以使用 . 連接,例如 MyApp.String,會擴充為原子 :"Elixir.MyApp.String"。點號實際上是名稱的一部分,但也可以用於組合。如果在程式碼中定義 alias MyApp.Example, as: Example,則 Example 會永遠擴充為 :"Elixir.MyApp.Example",而 Example.String 會擴充為 :"Elixir.MyApp.Example.String"

Elixir 的命名慣例 建議別名使用 CamelCase 格式。

模組屬性

模組屬性是模組特定的儲存,並寫成一元運算子 @ 與變數和區域呼叫的組合。例如,若要寫入名為 foo 的模組屬性,請使用 @foo "value",並使用 @foo 從中讀取。由於模組屬性是使用現有建構寫入的,因此遵循上述為運算子、變數和區域呼叫定義的相同規則。

區塊

區塊是多個 Elixir 表達式,由換行符號或分號分隔。隨時可以使用括號建立新的區塊。

左至右箭頭

左至右箭頭 (->) 用於建立左右之間的關係,通常稱為子句。左側可能有零、一或多個引數;右側是零、一或多個由換行符號分隔的表達式。 -> 可以在下列終止符號之間出現一次或多次: do-endfn-end(-)。當使用 -> 時,這些終止符號之間只允許其他子句。混合子句和一般表達式是不正確的語法。

casecond 建構中,出現在 doend 之間

case 1 do
  2 -> 3
  4 -> 5
end

cond do
  true -> false
end

() 之間的類型規格中

(integer(), boolean() -> integer())

也用於 fnend 之間,以建立匿名函式

fn
  x, y -> x + y
end

符號

符號以 ~ 開頭,後接一個小寫字母或一個或多個大寫字母,緊接著下列其中一組

  • ()
  • {}
  • []
  • <>
  • ""
  • ''
  • ||
  • //

在關閉這組後,可以提供零個或多個 ASCII 字母和數字作為修飾符。符號表示為非限定呼叫,加上 sigil_ 前綴,其中第一個引數是符號內容(字串),第二個引數是整數清單(修飾符)

如果 sigil 字母為大寫,則 sigil 中不允許內插,否則其內容可能是動態的。比較以下 sigil 的結果以獲取更多資訊

~s/f#{"o"}o/
~S/f#{"o"}o/

Sigil 可用於編碼具有其自身轉義規則的文字,例如正規表示式、日期時間等。

Elixir AST

Elixir 語法被設計為可以直接轉換為抽象語法樹 (AST)。Elixir 的 AST 是由以下元素組成的常規 Elixir 資料結構

  • 原子 - 例如 :foo
  • 整數 - 例如 42
  • 浮點數 - 例如 13.1
  • 字串 - 例如 "hello"
  • 清單 - 例如 [1, 2, 3]
  • 具有兩個元素的元組 - 例如 {"hello", :world}
  • 具有三個元素的元組,表示呼叫或變數,如下所述

Elixir AST 的構建區塊是呼叫,例如

sum(1, 2, 3)

它表示為具有三個元素的元組

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

第一個元素是原子(或另一個元組),第二個元素是具有元資料(例如行號)的兩個元素元組清單,第三個元素是引數清單。

我們可以透過呼叫 quote 來擷取任何 Elixir 表達式的 AST

quote do
  sum()
end
#=> {:sum, [], []}

變數也使用具有三個元素的元組和清單與原子的組合來表示,例如

quote do
  sum
end
#=> {:sum, [], Elixir}

您可以看到變數也使用元組表示,但第三個元素是表示變數內容的原子。

在本節中,我們將探討許多 Elixir 語法結構及其 AST 表示。

運算子

運算子被視為非限定呼叫

quote do
  1 + 2
end
#=> {:+, [], [1, 2]}

請注意,. 也是一個運算子。遠端呼叫在 AST 中使用具有兩個引數的點,其中第二個引數始終是原子

quote do
  foo.bar(1, 2, 3)
end
#=> {{:., [], [{:foo, [], Elixir}, :bar]}, [], [1, 2, 3]}

呼叫匿名函式在 AST 中使用具有單一引數的點,反映了函式名稱「遺失」在點的右側這一事實

quote do
  foo.(1, 2, 3)
end
#=> {{:., [], [{:foo, [], Elixir}]}, [], [1, 2, 3]}

別名

別名由 __aliases__ 呼叫表示,每個區段由點分隔作為引數

quote do
  Foo.Bar.Baz
end
#=> {:__aliases__, [], [:Foo, :Bar, :Baz]}

quote do
  __MODULE__.Bar.Baz
end
#=> {:__aliases__, [], [{:__MODULE__, [], Elixir}, :Bar, :Baz]}

除了第一個引數外,所有引數保證都是原子。

資料結構

請記住,清單是文字,因此它們在 AST 中表示為它們自己

quote do
  [1, 2, 3]
end
#=> [1, 2, 3]

元組有自己的表示,但兩個元素的元組除外,它們表示為它們自己

quote do
  {1, 2}
end
#=> {1, 2}

quote do
  {1, 2, 3}
end
#=> {:{}, [], [1, 2, 3]}

二進制表示形式類似於元組,但它們標記為 :<<>> 而不是 :{}

quote do
  <<1, 2, 3>>
end
#=> {:<<>>, [], [1, 2, 3]}

映射也是如此,其中對會被視為包含兩個元素的元組清單

quote do
  %{1 => 2, 3 => 4}
end
#=> {:%{}, [], [{1, 2}, {3, 4}]}

區塊

區塊表示為 __block__ 呼叫,其中每一行都是一個單獨的引數

quote do
  1
  2
  3
end
#=> {:__block__, [], [1, 2, 3]}

quote do 1; 2; 3; end
#=> {:__block__, [], [1, 2, 3]}

由左至右箭頭

由左至右箭頭 (->) 的表示形式類似於運算子,但它們總是清單的一部分,其左側表示引數清單,右側為表達式。

例如,在 casecond

quote do
  case 1 do
    2 -> 3
    4 -> 5
  end
end
#=> {:case, [], [1, [do: [{:->, [], [[2], 3]}, {:->, [], [[4], 5]}]]]}

quote do
  cond do
    true -> false
  end
end
#=> {:cond, [], [[do: [{:->, [], [[true], false]}]]]}

() 之間

quote do
  (1, 2 -> 3
   4, 5 -> 6)
end
#=> [{:->, [], [[1, 2], 3]}, {:->, [], [[4, 5], 6]}]

fnend 之間

quote do
  fn
    1, 2 -> 3
    4, 5 -> 6
  end
end
#=> {:fn, [], [{:->, [], [[1, 2], 3]}, {:->, [], [[4, 5], 6]}]}

限定元組

限定元組 (foo.{bar, baz}) 以 {:., [], [expr, :{}]} 呼叫表示,其中 expr 表示句點的左側,而引數表示大括號內的元素。這在 Elixir 中用於提供多個別名

quote do
  Foo.{Bar, Baz}
end
#=> {{:., [], [{:__aliases__, [], [:Foo]}, :{}]}, [], [{:__aliases__, [], [:Bar]}, {:__aliases__, [], [:Baz]}]}

選用語法

上述所有建構都是 Elixir 語法的一部分,並在 Elixir AST 中有自己的表示形式。本節將討論其餘建構,它們是上述建構的替代表示形式。換句話說,以下建構可以在 Elixir 程式碼中以多種方式表示,並保留 AST 等效性。我們稱之為「選用語法」。

有關 Elixir 選用語法的輕量級介紹,請參閱此文件。以下是更完整的參考。

其他進制中的整數和 Unicode 碼點

Elixir 允許整數包含 _ 以分隔數字,並提供便利性來表示其他進制中的整數

1_000_000
#=> 1000000

0xABCD
#=> 43981 (Hexadecimal base)

0o01234567
#=> 342391 (Octal base)

0b10101010
#=> 170 (Binary base)


#=> 233 (Unicode code point)

這些建構只存在於語法層級。上述所有範例在 AST 中都以其底層整數表示。

存取語法

存取語法表示為呼叫 Access.get/2

quote do
  opts[arg]
end
#=> {{:., [], [Access, :get]}, [], [{:opts, [], Elixir}, {:arg, [], Elixir}]}

選用括號

Elixir 提供選用括號於具有一個或多個參數的本機和遠端呼叫

quote do
  sum 1, 2, 3
end
#=> {:sum, [], [1, 2, 3]}

解析器將上述視為與 sum(1, 2, 3) 相同。您可以移除所有具有一個以上參數的呼叫的括號。

您也可以略過限定呼叫的括號,例如 Foo.bar 1, 2, 3。呼叫匿名函數時需要括號,例如 f.(1, 2, 3)

實際上,開發人員偏好為大多數呼叫加上括號。主要略過括號的時機是在 Elixir 的控制流程建構中,例如 defmoduleifcase 等,以及在某些 DSL 中。

關鍵字

Elixir 中的關鍵字是包含兩個元素的元組清單,其中第一個元素是原子。使用基本建構,它們將表示為

[{:foo, 1}, {:bar, 2}]

然而,Elixir 引進語法糖,其中上述關鍵字可以寫成如下

[foo: 1, bar: 2]

具有外來字元(例如空白)的原子必須用引號包住。此規則也適用於關鍵字

[{:"foo bar", 1}, {:"bar baz", 2}] == ["foo bar": 1, "bar baz": 2]

請記住,由於清單和兩個元素的元組是引號字面,因此根據定義,關鍵字也是字面(事實上,兩個元素的元組之所以是引號字面,就是為了支援關鍵字作為字面)。

為了成為有效的關鍵字語法,: 前面不能有任何空白(foo : 1 無效),並且後面必須有空白(foo:1 無效)。

關鍵字作為最後參數

Elixir 也支援一種語法,如果呼叫的最後參數是關鍵字清單,則可以略過方括號。這表示下列

if(condition, do: this, else: that)

等於

if(condition, [do: this, else: that])

而這又等於

if(condition, [{:do, this}, {:else, that}])

do-end 區塊

最後一個語法便利性是 do-end 區塊。do-end 區塊等於函數呼叫的最後參數為關鍵字,其中區塊內容以括號包住。例如

if true do
  this
else
  that
end

等於

if(true, do: (this), else: (that))

我們在上一節中已探討過。

括號對於支援多重表達式很重要。這

if true do
  this
  that
end

等於

if(true, do: (
  this
  that
))

do-end 區塊內,你可以引入其他關鍵字,例如在上述 if 中使用的 else。在 do-end 之間支援的關鍵字是靜態的,而且是

  • after
  • catch
  • else
  • rescue

你可以看到它們用於 receivetry 等建構中。