Erlang/Elixir語法速成

lbjfish 6年前發布 | 43K 次閱讀 Elixir Erlang開發 ErLang

Erlang/Elixir語法速成

本文是針對Erlang開發人員的Elixir語法簡介,同時也適用于試圖了解Erlang的Elixir開發者。

本文簡要介紹了Elixir/Erlang語法,互操作能力,在線文檔和示例代碼等最基礎的知識。

1 運行代碼

1.1 Erlang

最快速運行代碼的方法是啟動Erlang shell - erl 。本文中大部分代碼可以直接粘貼到shell中,

Erlang中的命名函數必須包含在模塊中,而且必須要在模塊編譯后才能使用。下面是一個模塊的例子:

% module_name.erl
-module(module_name).  % you may use some other name
-compile(export_all).

hello() -> io:format("~s~n", ["Hello world!"]).</code></pre>

編輯文件并保存,在同一目錄下運行 erl ,并執行 編譯 命令

Eshell V5.9  (abort with ^G)
1> c(module_name).
ok
1> module_name:hello().
Hello world!
ok

shell運行的同時也可以編輯文件。 但不要忘記執行 c(module_name) 來加載最新的更改。

要注意,文件名必須與在 -module() 中聲明的文件名保持一致,擴展名為 .erl 。

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</code></pre>

    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中, 原子 是以小寫字母開頭的任意標志符,例如 ok 、 tuple 、 donut .

    以大寫字母開頭的標識符則會被視為變量名。而在Elixir中,前者被用于變量名,而后者則被視為原子的別名。

    Elixir中的原子始終以冒號 : 作為首字符。

    Erlang

    im_an_atom.
    me_too.

Im_a_var. X = 10.</code></pre>

Elixir

:im_an_atom
:me_too

im_a_var x = 10

Module # 稱為原子別名; 展開后是 :'Elixir.Module'</code></pre>

非小寫字母開頭的標識符也可以作為原子。 不過兩種語言的語法有所不同:

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不會導入默認的 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}

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</code></pre>

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</code></pre>

3.5 映射(Map)

Erlang R17中引入了映射,一種無序的鍵-值數據結構。鍵和值可以是任意的數據類型,

映射的創建、更新和模式匹配如下所示:

Erlang

Map = #{key => 0}.
Updated = Map#{key := 1}.

{key := Value} = Updated.

Value =:= 1. %=> true</code></pre>

Elixir

map = %{:key => 0}
map = %{map | :key => 1}
%{:key => value} = map
value === 1

=> true</code></pre>

當鍵為原子時,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. 流程控制

if 和 case 結構在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中的真值概念(除了 nil 和 false 皆為真值),而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的 入門指南在線文檔

 

來自:https://segmentfault.com/a/1190000012776435

 

 本文由用戶 lbjfish 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!