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的入门指南在线文档

相关文章
相关标签/搜索