Erlang/Elixir Syntax: A Crash Course

这是针对Erlang开发人员的Elixir语法的快速介绍,反之亦然. 这是您了解Elixir / Erlang代码,支持互操作性,阅读文档,示例代码等所需的绝对最少知识.

Running code

Erlang

运行某些代码的最快方法是启动Erlang shell – erl . 该页面上的许多代码片段都可以直接粘贴到外壳中. 但是,当您要定义命名函数时,Erlang希望它位于模块内部,并且必须对模块进行编译. 这是模块的骨架:

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

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

向其中添加功能,将其保存到磁盘,从同一目录运行erl并执行compile命令:

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

编辑文件时,可以保持外壳程序运行. 只是不要忘记执行c(module_name)来加载最新的更改. 请注意,文件名必须与-module()指令中声明的文件名相同,并带有扩展名.erl .

Elixir

Elixir也有一个名为iex的交互式外壳. 可以使用elixirc (类似于Erlang的erlc )来完成Elixir代码的编译. 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中,您无需仅创建文件就可以创建新模块,可以在Shell中直接定义Elixir模块:

defmodule MyModule do
  def hello do
    IO.puts "Another Hello"
  end
end

Notable differences

本节介绍了两种语言之间的一些语法差异.

Operator names

一些运算符的拼写有所不同.

Erlang Elixir Meaning
and 无法使用 逻辑"与",计算两个参数
andalso and 逻辑"与",短路
or 无法使用 逻辑"或",计算两个参数
orelse or 逻辑"或",短路
=:= === 匹配运算符
=/= !== 否定的比赛
/= != 不等于
=< <= 小于或等于

Delimiters

Erlang表达式以点结尾. 和逗号,用于在一个上下文中(例如,在函数定义中)评估多个表达式. 在Elixir中,表达式由换行符或分号分隔; .

Erlang

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

Elixir

x = 2; y = 3
x + y

Variable names

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

Calling functions

从模块调用功能使用不同的语法. 在Erlang中,您将编写

lists:last([1, 2]).

List模块调用last函数. 在Elixir中,使用点. 代替冒号:

List.last([1, 2])

注意 . 由于Erlang模块由原子表示,因此您可以按以下方式在Elixir中调用Erlang函数:

:lists.sort([3, 2, 1])

所有的Erlang内置组件都位于:erlang模块中.

Data types

Erlang和Elixir在大多数情况下具有相同的数据类型,但是存在许多差异.

Atoms

在Erlang中, atom是任何以小写字母开头的标识符,例如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  # this is called an atom alias; it expands to :'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

Tuples

元组的语法在两种语言中都相同,但是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}

Lists and binaries

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

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

Maps

Erlang R17引入了maps,这是一个键值存储,无需排序. 键和值可以是任何术语. 创建,更新和匹配两种语言的地图如下所示:

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

If the keys are all atoms, Elixir allows developers to use key: 0 for defining the map as well as using .key for accessing fields:

map = %{key: 0}
map = %{map | key: 1}
map.key === 1

Regular expressions

Elixir支持正则表达式的文字语法. 这种语法允许正则表达式在编译时而不是在运行时进行编译,并且不需要您对特殊的正则表达式字符进行两次转义:

Erlang

{ ok, Pattern } = re:compile("abc\\s").
re:run("abc ", Pattern).
%=> { match, ["abc "] }

Elixir

Regex.run ~r/abc\s/, "abc "
#=> ["abc "]

Heredocs还支持正则表达式,在定义多行正则表达式时非常方便:

Regex.regex? ~r"""
This is a regex
spanning several
lines.
"""
#=> true

Modules

Each Erlang module lives in its own file which has the following structure:

-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指令调用前两个函数. 它包含一个函数列表,每个函数都以<function name>/<arity>格式编写. Arity代表参数数量.

与上面的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

Function syntax

Erlang书籍的这一章详细介绍了Erlang中的模式匹配和函数语法. 在这里,我简要介绍要点,并提供Erlang和Elixir中的示例代码.

Pattern matching

Elixir中的模式匹配基于Erlang的实现,通常非常相似:

Erlang

loop_through([Head | Tail]) ->
  io:format('~p~n', [Head]),
  loop_through(Tail);

loop_through([]) ->
  ok.

Elixir

def loop_through([head | tail]) do
  IO.inspect head
  loop_through tail
end

def loop_through([]) do
  :ok
end

当多次定义具有相同名称的函数时,每个这样的定义都称为子句 . 在Erlang中,子句始终并排并用分号分隔; . 最后一个子句以点结尾. .

Elixir不需要标点符号来分隔子句,但必须将它们分组在一起.

Identifying functions

在Erlang和Elixir中,功能不仅由其名称标识,而且由其名称和Arity标识. 在下面的两个示例中,我们定义了四个不同的函数(都命名为sum ,但具有不同的arity):

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

保护表达式提供了一种简洁的方法来定义基于某些条件接受有限值集的函数.

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"

Default values

此外,Elixir允许为参数提供默认值,而Erlang不允许.

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

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

Anonymous functions

匿名函数的定义方式如下:

Erlang

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]

Elixir

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]

在定义匿名函数时,也可以使用模式匹配.

Erlang

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"

Elixir

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 functions

匿名函数是一等值,因此它们可以作为参数传递给其他函数,也可以用作返回值. 有一种特殊的语法允许以相同的方式对待命名函数.

Erlang

% math.erl
-module(math).
-export([square/1]).

square(X) -> X * X.
Eshell V5.9  (abort with ^G)
1> c(math).
{ok,math}
2> lists:map(fun math:square/1, [1, 2, 3]).
[1,4,9]

Elixir

defmodule Math do
  def square(x) do
    x * x
  end
end

Enum.map [1, 2, 3], &Math.square/1
#=> [1, 4, 9]

Partials and function captures in Elixir

Elixir支持部分功能应用,这些功能可用于以简洁的方式定义匿名功能:

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 .

Control flow

ifcase构造实际上是Erlang和Elixir中的表达式,但可以像命令式语言一样用于控制流.

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

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

if ,Elixir的cond和Erlang的cond有两个重要区别:

1) cond允许左侧的任何表达式,而Erlang仅允许保护子句;

2) cond使用Elixir的true和falsy值的概念(除nilfalse外,其他都是真理),Erlang的if严格地期望布尔值;

Elixir还提供了一个if函数,该函数类似于更多命令式语言,在需要检查一个子句为真还是假时非常有用:

if x > 10 do
  :greater_than_ten
else
  :not_greater_than_ten
end

Sending and receiving messages

在Erlang和Elixir之间,发送和接收的语法仅稍有不同.

Erlang

Pid = self().

Pid ! {hello}.

receive
  {hello} -> ok;
  Other -> Other
after
  10 -> timeout
end.

Elixir

pid = Kernel.self

send pid, {:hello}

receive do
  {:hello} -> :ok
  other -> other
after
  10 -> :timeout
end

Adding Elixir to existing Erlang programs

Elixir编译为BEAM字节码(通过Erlang抽象格式). 这意味着可以从Erlang调用Elixir代码,反之亦然,而无需编写任何绑定. 所有Elixir模块均以Elixir开头Elixir. 前缀后跟常规的Elixir名称. 例如,以下是在Erlang中使用Elixir的UTF-8感知String小写的方法:

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

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

Rebar integration

如果使用钢筋,则应该能够将Elixir git存储库包含为依赖项:

https://github.com/elixir-lang/elixir.git

Elixir的结构类似于Erlang的OTP. 如其源代码存储库所示,它分为放置在lib目录中的应用程序. 由于rebar.config无法识别这种结构,因此我们需要在rebar.config显式添加我们要使用的Elixir应用程序,例如:

{lib_dirs, [
  "deps/elixir/lib"
]}.

这应该足以直接从您的Erlang代码调用Elixir函数. 如果您还要编写Elixir代码,则可以安装Elixir的rebar插件进行自动编译 .

Manual integration

如果您不使用钢筋,则在现有的Erlang软件中使用Elixir的最简单方法是使用《 入门指南》中指定的不同方法之一安装Elixir,并将结帐中的lib目录添加到ERL_LIBS .

Further reading

Erlang的官方文档站点上有大量的编程示例. 将它们翻译成Elixir可能是个好习惯.

Elixir还提供了" 入门指南"在线提供了文档 .

by  ICOPY.SITE