檢視原始碼 語法參考
Elixir 語法設計為可直接轉換成抽象語法樹 (AST)。這表示 Elixir 語法大多是統一的,僅有少數「語法糖」結構可減少常見 Elixir 慣用語法中的雜訊。
本文件涵蓋所有 Elixir 語法結構作為參考,並討論其確切的 AST 表示。
保留字
以下是 Elixir 語言中的保留字。它們在整份指南中都有詳細說明,但在此處簡要說明以方便閱讀
true
、false
、nil
- 用作原子when
、and
、or
、not
、in
- 用作運算子fn
- 用於匿名函式定義do
、end
、catch
、rescue
、after
、else
- 用於 do-end 區塊
資料類型
數字
Elixir 中的整數 (1234
) 和浮點數 (123.4
) 表示為一連串數字,這些數字可用底線分隔以提高可讀性,例如 1_000_000
。整數在表示中從不包含小數點 (.
)。浮點數包含小數點,且小數點後至少有一個其他數字。浮點數也支援科學記號,例如 123.4e10
或 123.4E10
。
原子
未加引號的原子以冒號 (:
) 開頭,冒號後必須緊接 Unicode 字母或底線。原子可以繼續使用 Unicode 字母、數字、底線和 @
的序列。原子可以用 !
或 ?
結尾。有效的未加引號原子為::ok
、:ISO8601
和 :integer?
。
如果冒號後緊接一對雙引號或單引號,並將原子名稱括起來,則該原子視為已加引號。與未加引號的原子不同,此原子可以由任何 Unicode 字元組成(不只字母),例如 :'🌢 Elixir'
、:"++olá++"
和 :"123"
。
具有相同名稱的加引號和未加引號原子被視為等效,因此 :atom
、:"atom"
和 :'atom'
表示相同的原子。唯一的缺點是,當在不需要加引號的原子中使用引號時,編譯器會發出警告。
Elixir 中的所有運算子都是有效的原子。有效的範例為 :foo
、:FOO
、:foo_42
、:foo@bar
和 :++
。無效的範例為 :@foo
(開頭不允許 @
)、:123
(開頭不允許數字)和 :(*)
(不是有效的運算子)。
true
、false
和 nil
是保留字,分別由原子 :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
-end
、fn
-end
或 (
-)
。當使用 ->
時,這些終止符號之間只允許其他子句。混合子句和一般表達式是不正確的語法。
在 case
和 cond
建構中,出現在 do
和 end
之間
case 1 do
2 -> 3
4 -> 5
end
cond do
true -> false
end
在 (
和 )
之間的類型規格中
(integer(), boolean() -> integer())
也用於 fn
和 end
之間,以建立匿名函式
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]}
由左至右箭頭
由左至右箭頭 (->
) 的表示形式類似於運算子,但它們總是清單的一部分,其左側表示引數清單,右側為表達式。
例如,在 case
和 cond
中
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]}]
在 fn
和 end
之間
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 的控制流程建構中,例如 defmodule
、if
、case
等,以及在某些 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
你可以看到它們用於 receive
、try
等建構中。