Erlang/Elixir語法速成

原文:Erlang/Elixir Syntax: A Crash Course

Erlang/Elixir語法速成

本文是針對Erlang開發人員的Elixir語法簡介,同時也適用於試圖瞭解Erlang的Elixir開發者。
本文簡要介紹了Elixir/Erlang語法,互操做能力,在線文檔和示例代碼等最基礎的知識。html

  1. 運行代碼git

    1. Erlang
    2. Elixir
  2. 顯著差別github

    1. 操做符名稱
    2. 分隔符
    3. 變量名
    4. 函數調用
  3. 數據類型正則表達式

    1. 原子
    2. 元組
    3. 列表與二進制串
    4. 關鍵字列表(Keyword list)
    5. 映射(Map)
    6. 正則表達式
  4. 模塊
  5. 函數語法shell

    1. 模式匹配
    2. 函數識別
    3. 默認值
    4. 匿名函數
    5. 做爲一等公民的函數
    6. Elixir中的局部應用與函數捕捉
  6. 流程控制編程

    1. Case
    2. If
    3. 發送和接收消息
  7. 將Elixir添加到已有的Erlang程序中數據結構

    1. 使用Rebar集成
    2. 手動集成
  8. 擴展閱讀

1 運行代碼

1.1 Erlang

最快速運行代碼的方法是啓動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()中聲明的文件名保持一致,擴展名爲.erlide

1.2 Elixir

與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

2 顯著差別

這一節討論了兩種語言之間的一些語法差別。

2.1 操做符名稱

部分操做符采用了不一樣的書寫方式

ERLANG ELIXIR 含義
and 不可用 邏輯‘與’,所有求值
andalso and 邏輯‘與’,採用短路求值策略
or 不可用 邏輯‘或’,所有求值
orelse or 邏輯‘與’,採用短路求值策略
=:= === 全等
=/= !== 不全等
/= != 不等於
=< <= 小於等於

2.2 分隔符

Erlang表達式使用點號.做爲結束,逗號,用來分割同一上下文中的多個表達式(例如在函數定義中)。
在Elixir中,表達式由換行符或分號;分隔。

Erlang

X = 2, Y = 3.
X + Y.

Elixir

x = 2; y = 3
x + y

2.3 變量名

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

2.4 函數調用

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模塊中

3 數據類型

Erlang和Elixir的數據類型大部分都相同,但依然有一些差別。

3.1 原子

Erlang中,原子是以小寫字母開頭的任意標誌符,例如oktupledonut.
以大寫字母開頭的標識符則會被視爲變量名。而在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

3.2 元組

兩種語言元組的語法是相同的,不過API有所不一樣,Elixir嘗試使用下面的方法規範化Erlang庫:

  1. 函數的subject老是做爲第一個參數。
  2. 全部對數據結構的操做均基於零進行。

也就是說,Elixir不會導入默認的elementsetelement函數,而是提供elemput_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}

3.3 列表與二進制串

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

3.4 關鍵字列表(Keyword list)

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

3.5 映射(Map)

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

3.6 正則表達式

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

4 模塊

每一個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

5 函數語法

「Learn You Some Erlang」書中的這一章詳細講解了Erlang的模式匹配和函數語法。
而本文只簡要介紹主要內容並展現部分示例代碼。

5.1 模式匹配

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不須要經過符號來分隔子句,不過要求子句必須按順序寫在一塊兒。

5.2 函數識別

在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"

5.3 默認值

Elixir容許參數具備默認值,而Erlang不容許。

def mul_by(x, n \\ 2) do
  x * n
end

mul_by 4, 3 #=> 12
mul_by 4    #=> 8

5.4 匿名函數

定義匿名函數:

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"

5.5 做爲一等公民(first-class)的函數

匿名函數是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]

5.6 Elixir中的局部應用與函數捕捉

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

6. 流程控制

ifcase結構在Erlang和Elixir中其實是表達式,不過依然能夠像命令式語言的語句那樣,用於流程控制

6.1 Case

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

6.2 If

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中的真值概念(除了nilfalse皆爲真值),而Erlang的if則嚴格的指望一個布爾值;

Elixir一樣提供了一個相似於命令式語言中if的功能,用於檢查一個子句是true仍是false:

if x > 10 do
  :greater_than_ten
else
  :not_greater_than_ten
end

6.3 發送和接收消息

發送和接收消息的語法僅略有不一樣:

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

7 將Elixir添加到已有的Erlang程序中

Elixir會被編譯成BEAM字節碼(經過Erlang抽象格式)。這意味着Elixir和Erlang的代碼能夠互相調用而不須要添加其餘任何綁定。
Erlang代碼中使用Elixir模塊需要以Elixir.做爲前綴,而後將Elixir的調用附在其後。
例如,這裏演示了在Erlang中如何使用Elixir的String模塊:

-module(bstring).
-export([downcase/1]).

downcase(Bin) ->
  'Elixir.String':downcase(Bin).

7.1 使用Rebar集成

若是使用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插件

7.2 手動集成

若是不使用rebar,在已有Erlang軟件中使用Elixir的最簡單的方式是按照入門指南中的方法安裝Elixir,而後將lib添目錄加到ERL_LIBS中。

8 擴展閱讀

Erlang的官方文檔網站有不錯的編程示例集,把它們從新用Elixir實現一遍是不錯的練習方法。
Erlang cookbook也提供了更多有用的代碼示例。

還能夠進一步閱讀Elixir的入門指南在線文檔

相關文章
相關標籤/搜索