本文是針對Erlang開發人員的Elixir語法簡介,同時也適用於試圖瞭解Erlang的Elixir開發者。
本文簡要介紹了Elixir/Erlang語法,互操做能力,在線文檔和示例代碼等最基礎的知識。html
最快速運行代碼的方法是啓動Erlang shell - erl
。本文中大部分代碼能夠直接粘貼到shell中,
Erlang中的命名函數必須包含在模塊中,並且必需要在模塊編譯後才能使用。下面是一個模塊的例子:app
% module_name.erl -module(module_name). % you may use some other name -compile(export_all). hello() -> io:format("~s~n", ["Hello world!"]).
編輯文件並保存,在同一目錄下運行erl
,並執行編譯
命令less
Eshell V5.9 (abort with ^G) 1> c(module_name). ok 1> module_name:hello(). Hello world! ok
shell運行的同時也能夠編輯文件。 但不要忘記執行c(module_name)
來加載最新的更改。
要注意,文件名必須與在-module()
中聲明的文件名保持一致,擴展名爲.erl
。ide
與Erlang相似,Elixir有一個名爲iex
的交互式shell。編譯Elixir代碼可使用elixirc
(相似於Erlang的erlc
)。
Elixir還提供一個名爲elixir
的可執行文件來運行Elixir代碼。上面的模塊用Elixir來寫就是這樣:
# module_name.ex defmodule ModuleName do def hello do IO.puts "Hello World" end end
而後,在iex
中編譯:
Interactive Elixir iex> c("module_name.ex") [ModuleName] iex> ModuleName.hello Hello world! :ok
要注意的是,在Elixir中,不要求模塊必須保存在文件中,Elixir的模塊能夠直接在shell中定義:
defmodule MyModule do def hello do IO.puts "Another Hello" end end
這一節討論了兩種語言之間的一些語法差別。
部分操做符采用了不一樣的書寫方式
ERLANG | ELIXIR | 含義 |
---|---|---|
and | 不可用 | 邏輯‘與’,所有求值 |
andalso | and | 邏輯‘與’,採用短路求值策略 |
or | 不可用 | 邏輯‘或’,所有求值 |
orelse | or | 邏輯‘與’,採用短路求值策略 |
=:= | === | 全等 |
=/= | !== | 不全等 |
/= | != | 不等於 |
=< | <= | 小於等於 |
Erlang表達式使用點號.
做爲結束,逗號,
用來分割同一上下文中的多個表達式(例如在函數定義中)。
在Elixir中,表達式由換行符或分號;
分隔。
Erlang
X = 2, Y = 3. X + Y.
Elixir
x = 2; y = 3 x + y
Erlang中的變量只能被綁定一次,Erlang shell提供一個特殊的命令f
,用於刪除某個變量或全部變量的綁定。
Elixir容許變量被屢次賦值,若是但願匹配變量以前的值,應當使用^
。
Erlang
Eshell V5.9 (abort with ^G) 1> X = 10. 10 2> X = X + 1. ** exception error: no match of right hand side value 11 3> X1 = X + 1. 11 4> f(X). ok 5> X = X1 * X1. 121 6> f(). ok 7> X. * 1: variable 'X' is unbound 8> X1. * 1: variable 'X1' is unbound
Elixir
iex> a = 1 1 iex> a = 2 2 iex> ^a = 3 ** (MatchError) no match of right hand side value: 3
Elixir容許在函數調用中省略括號,而Erlang不容許。
ERLANG | ELIXIR |
---|---|
some_function(). | some_function |
sum(A, B) | sum a, b |
調用模塊中的函數的語法不一樣,Erlang中,下面的代碼
lists : last ([ 1 , 2 ]).
表示從list
模塊中調用函數last
,而在Elixir中,使用點號.
代替冒號:
List.last([1, 2])
注意 在Elixir中,因爲Erlang的模塊用原子表示,所以用以下方法調用Erlang的函數:
:lists.sort [3, 2, 1]
全部Erlang的內置函數都包含在:erlang
模塊中
Erlang和Elixir的數據類型大部分都相同,但依然有一些差別。
Erlang中,原子
是以小寫字母開頭的任意標誌符,例如ok
、tuple
、donut
.
以大寫字母開頭的標識符則會被視爲變量名。而在Elixir中,前者被用於變量名,然後者則被視爲原子的別名。
Elixir中的原子始終以冒號:
做爲首字符。
Erlang
im_an_atom. me_too. Im_a_var. X = 10.
Elixir
:im_an_atom :me_too im_a_var x = 10 Module # 稱爲原子別名; 展開後是 :'Elixir.Module'
非小寫字母開頭的標識符也能夠做爲原子。 不過兩種語言的語法有所不一樣:
Erlang
is_atom(ok). %=> true is_atom('0_ok'). %=> true is_atom('Multiple words'). %=> true is_atom(''). %=> true
Elixir
is_atom :ok #=> true is_atom :'ok' #=> true is_atom Ok #=> true is_atom :"Multiple words" #=> true is_atom :"" #=> true
兩種語言元組的語法是相同的,不過API有所不一樣,Elixir嘗試使用下面的方法規範化Erlang庫:
subject
老是做爲第一個參數。也就是說,Elixir不會導入默認的element
和setelement
函數,而是提供elem
和put_elem
做爲替代:
Erlang
element(1, {a, b, c}). %=> a setelement(1, {a, b, c}, d). %=> {d, b, c}
Elixir
elem({:a, :b, :c}, 0) #=> :a put_elem({:a, :b, :c}, 0, :d) #=> {:d, :b, :c}
Elixir具備訪問二進制串的快捷語法:
Erlang
is_list('Hello'). %=> false is_list("Hello"). %=> true is_binary(<<"Hello">>). %=> true
Elixir
is_list 'Hello' #=> true is_binary "Hello" #=> true is_binary <<"Hello">> #=> true <<"Hello">> === "Hello" #=> true
Elixir中,字符串意味着一個UTF-8編碼的二進制串,String
模塊可用於處理字符串。
同時Elixir也但願程序源碼採用UTF-8編碼。而在Erlang中,字符串表示字符的列表,:string
模塊用於處理它們,但並無採用UTF-8編碼。
Elixir還支持多行字符串(也被稱爲heredocs):
is_binary """ This is a binary spanning several lines. """ #=> true
Elixir中,若是列表是由具備兩個元素的元組組成,而且每一個元組中的第一個元素是原子,則稱這樣的列表爲關鍵字列表:
Erlang
Proplist = [{another_key, 20}, {key, 10}]. proplists:get_value(another_key, Proplist). %=> 20
Elixir
kw = [another_key: 20, key: 10] kw[:another_key] #=> 20
Erlang R17中引入了映射,一種無序的鍵-值數據結構。鍵和值能夠是任意的數據類型,
映射的建立、更新和模式匹配以下所示:
Erlang
Map = #{key => 0}. Updated = Map#{key := 1}. #{key := Value} = Updated. Value =:= 1. %=> true
Elixir
map = %{:key => 0} map = %{map | :key => 1} %{:key => value} = map value === 1 #=> true
當鍵爲原子時,Elixir可使用key: 0
來定義映射,使用.key
來訪問值:
map = %{key: 0} map = %{map | key: 1} map.key === 1
Elixir支持正則表達式語法,容許在編譯(elixir源碼)時編譯正則表達式,
而不是等到運行時再進行編譯。並且對於特殊的字符,也無需進行屢次轉義:
Erlang
{ ok, Pattern } = re:compile("abc\\s"). re:run("abc ", Pattern). %=> { match, ["abc "] }
Elixir
Regex.run ~r/abc\s/, "abc " #=> ["abc "]
支持在heredocs中書寫正則,這樣便於理解複雜正則
Elixir
Regex.regex? ~r""" This is a regex spanning several lines. """ #=> true
每一個Erlang模塊都保存在與其同名的文件中,具備如下結構:
-module(hello_module). -export([some_fun/0, some_fun/1]). % A "Hello world" function some_fun() -> io:format('~s~n', ['Hello world!']). % This one works only with lists some_fun(List) when is_list(List) -> io:format('~s~n', List). % Non-exported functions are private priv() -> secret_info.
在這裏,咱們建立了一個名爲hello_module
的模塊。模塊中定義了三個函數,
頂部的export
指令導出了前兩個函數,讓它們可以被其餘模塊調用。export
指令裏包含了須要導出函數的列表,
其中每一個函數都寫做<函數名>/<元數>
的形式。在這裏,元數表示函數參數的個數。
和上述Erlang代碼做用相同的Elixir代碼:
defmodule HelloModule do # A "Hello world" function def some_fun do IO.puts "Hello world!" end # This one works only with lists def some_fun(list) when is_list(list) do IO.inspect list end # A private function defp priv do :secret_info end end
在Elixir中,一個文件中能夠包含多個模塊,而且還容許嵌套定義模塊:
defmodule HelloModule do defmodule Utils do def util do IO.puts "Utilize" end defp priv do :cant_touch_this end end def dummy do :ok end end defmodule ByeModule do end HelloModule.dummy #=> :ok HelloModule.Utils.util #=> "Utilize" HelloModule.Utils.priv #=> ** (UndefinedFunctionError) undefined function: HelloModule.Utils.priv/0
「Learn You Some Erlang」書中的這一章詳細講解了Erlang的模式匹配和函數語法。
而本文只簡要介紹主要內容並展現部分示例代碼。
Elixir中的模式匹配基於於Erlang實現,二者一般很是相似:
Erlang
loop_through([H | T]) -> io:format('~p~n', [H]), loop_through(T); loop_through([]) -> ok.
Elixir
def loop_through([h | t]) do IO.inspect h loop_through t end def loop_through([]) do :ok end
當屢次定義名稱相同的函數時,每一個這樣的定義稱爲子句 。
在Erlang中,子句老是按順序寫在一塊兒並使用分號;
分隔 。 最後一個子句用點號.
結束。
Elixir不須要經過符號來分隔子句,不過要求子句必須按順序寫在一塊兒。
在Erlang和Elixir中,僅憑函數名是沒法區分一個函數的。必須經過函數名和元數加以區分。
下面兩個例子中,咱們定義了四個不一樣的函數(全部名字都爲sum
,但它們具備不一樣的元數):
Erlang
sum() -> 0. sum(A) -> A. sum(A, B) -> A + B. sum(A, B, C) -> A + B + C.
Elixir
def sum, do: 0 def sum(a), do: a def sum(a, b), do: a + b def sum(a, b, c), do: a + b + c
Guard表達式提供了一種簡明的方法來定義在不一樣條件下接受有限個數參數的函數。
Erlang
sum(A, B) when is_integer(A), is_integer(B) -> A + B; sum(A, B) when is_list(A), is_list(B) -> A ++ B; sum(A, B) when is_binary(A), is_binary(B) -> <<A/binary, B/binary>>. sum(1, 2). %=> 3 sum([1], [2]). %=> [1, 2] sum("a", "b"). %=> "ab"
Elixir
def sum(a, b) when is_integer(a) and is_integer(b) do a + b end def sum(a, b) when is_list(a) and is_list(b) do a ++ b end def sum(a, b) when is_binary(a) and is_binary(b) do a <> b end sum 1, 2 #=> 3 sum [1], [2] #=> [1, 2] sum "a", "b" #=> "ab"
Elixir容許參數具備默認值,而Erlang不容許。
def mul_by(x, n \\ 2) do x * n end mul_by 4, 3 #=> 12 mul_by 4 #=> 8
定義匿名函數:
Sum = fun(A, B) -> A + B end. Sum(4, 3). %=> 7 Square = fun(X) -> X * X end. lists:map(Square, [1, 2, 3, 4]). %=> [1, 4, 9, 16]
sum = fn(a, b) -> a + b end sum.(4, 3) #=> 7 square = fn(x) -> x * x end Enum.map [1, 2, 3, 4], square #=> [1, 4, 9, 16]
定義匿名函數時也可使用模式匹配。
F = fun(Tuple = {a, b}) -> io:format("All your ~p are belong to us~n", [Tuple]); ([]) -> "Empty" end. F([]). %=> "Empty" F({a, b}). %=> "All your {a, b} are belong to us"
f = fn {:a, :b} = tuple -> IO.puts "All your #{inspect tuple} are belong to us" [] -> "Empty" end f.([]) #=> "Empty" f.({:a, :b}) #=> "All your {:a, :b} are belong to us"
匿名函數是first-class values,所以它們能夠看成參數傳遞給其餘函數,也能夠被看成返回值。
對於命名函數,可使用以下語法實現上述功能。
-module(math). -export([square/1]). square(X) -> X * X. lists:map(fun math:square/1, [1, 2, 3]). %=> [1, 4, 9]
defmodule Math do def square(x) do x * x end end Enum.map [1, 2, 3], &Math.square/1 #=> [1, 4, 9]
Elixir能夠利用函數的局部應用(partial application),以簡潔的方式定義匿名函數:
Enum.map [1, 2, 3, 4], &(&1 * 2) #=> [2, 4, 6, 8] List.foldl [1, 2, 3, 4], 0, &(&1 + &2) #=> 10
函數捕捉一樣使用&
操做符,它使得命名函數能夠做爲參數傳遞。
defmodule Math do def square(x) do x * x end end Enum.map [1, 2, 3], &Math.square/1 #=> [1, 4, 9]
上面的代碼至關於Erlang的fun math:square/1
。
if
和case
結構在Erlang和Elixir中其實是表達式,不過依然能夠像命令式語言的語句那樣,用於流程控制
case
結構是徹底基於模式匹配的流程控制。
Erlang
case {X, Y} of {a, b} -> ok; {b, c} -> good; Else -> Else end
Elixir
case {x, y} do {:a, :b} -> :ok {:b, :c} -> :good other -> other end
Erlang
Test_fun = fun (X) -> if X > 10 -> greater_than_ten; X < 10, X > 0 -> less_than_ten_positive; X < 0; X =:= 0 -> zero_or_negative; true -> exactly_ten end end. Test_fun(11). %=> greater_than_ten Test_fun(-2). %=> zero_or_negative Test_fun(10). %=> exactly_ten
Elixir
test_fun = fn(x) -> cond do x > 10 -> :greater_than_ten x < 10 and x > 0 -> :less_than_ten_positive x < 0 or x === 0 -> :zero_or_negative true -> :exactly_ten end end test_fun.(44) #=> :greater_than_ten test_fun.(0) #=> :zero_or_negative test_fun.(10) #=> :exactly_ten
Elixir的cond
和Erlang的if
有兩個重要的區別:
cond
容許左側爲任意表達式,而Erlang只容許Guard子句;cond
使用Elixir中的真值概念(除了nil
和false
皆爲真值),而Erlang的if
則嚴格的指望一個布爾值;Elixir一樣提供了一個相似於命令式語言中if
的功能,用於檢查一個子句是true仍是false:
if x > 10 do :greater_than_ten else :not_greater_than_ten end
發送和接收消息的語法僅略有不一樣:
Pid = self(). Pid ! {hello}. receive {hello} -> ok; Other -> Other after 10 -> timeout end.
pid = Kernel.self send pid, {:hello} receive do {:hello} -> :ok other -> other after 10 -> :timeout end
Elixir會被編譯成BEAM字節碼(經過Erlang抽象格式)。這意味着Elixir和Erlang的代碼能夠互相調用而不須要添加其餘任何綁定。
Erlang代碼中使用Elixir模塊需要以Elixir.
做爲前綴,而後將Elixir的調用附在其後。
例如,這裏演示了在Erlang中如何使用Elixir的String
模塊:
-module(bstring). -export([downcase/1]). downcase(Bin) -> 'Elixir.String':downcase(Bin).
若是使用rebar,應當把Elixir的git倉庫引入並做爲依賴添加:
https://github.com/elixir-lang/elixir.git
Elixir的結構與Erlang的OTP相似,被分爲不一樣的應用放在lib
目錄下,能夠在Elixir源碼倉庫中看到這種結構。
因爲rebar沒法識別這種結構,所以須要在rebar.config
中明確的配置所須要的Elixir應用,例如:
{lib_dirs, [ "deps/elixir/lib" ]}.
這樣就能直接從Erlang調用Elixir代碼了,若是須要編寫Elixir代碼,還應安裝自動編譯Elixir的rebar插件。
若是不使用rebar,在已有Erlang軟件中使用Elixir的最簡單的方式是按照入門指南中的方法安裝Elixir,而後將lib
添目錄加到ERL_LIBS
中。
Erlang的官方文檔網站有不錯的編程示例集,把它們從新用Elixir實現一遍是不錯的練習方法。
Erlang cookbook也提供了更多有用的代碼示例。