在这一小节我们将学习一部分Erlang的语言特性,目的是为了让你能够更好的理解在后面编写的插件的代码。
Erlang简介
Erlang 诞生于 1987 年,由爱立信的 CS-Lab 开发。 Erlang 是一种动态类型的、函数式的语言,主要是为了处理并行,分布式应用而设计的,所以 Erlang 内建了轻量级的进程模型,让编写并发应用变得非常容易。
运行 Erlang 程序需要有一个类似 JVM 的虚拟机,上一课我们已经安装了这个虚拟机,Erlang 提供了一个交互式的脚本解析器,我们可以运行 erl 打开这个解析器,并在里面运行 Erlang 语句:
Erlang/OTP 22 [erts-10.4.1] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe] [dtrace]
Eshell V10.4.1 (abort with ^G)
1> 1+1.
2
这里 Erlang 计算了 1+1 的值,输出结果 2。注意一条 Erlang 语句的结束符是英文句号”.”,和我们写文章时的句子结束符是一样的。
下面让我们来了解更多 Erlang 语言的特性。
变量和赋值
Erlang 中的”变量”一定是用大写字符开始的,比如大写 X 就是一个合法的”变量”,而小写 x 就不是一个合法的”变量”,我们可以用”变量赋值”:
2> X=10.
10
这里变量和赋值是加了引号的,原因是,在 Erlang 的世界里,其实没有变量和赋值这个两个概念。
我们可以尝试改变 X 的值:
3> X=1.
** exception error: no match of right hand side value 1
在 X 被第一次”赋值”后,就无法再改变它的值了,所以说 Erlang 里面的变量实际上是一次性赋值变量,本课程中我们仍然会用变量来指代它,不过你要记住它是不可变的。
在我们尝试修改 X 值时,我们得到一个错误提示:”no match of right hand side value 1”,这意思是等式两边的值不匹配,而不是你不能改变 X 的值,这是为什么呢?
因为 “=” 在 Erlang 中并不是赋值符,Erlang 中的 “=” 就非常类似我们在代数课程中使用的 “=”,用于表达一个等式,这是 Erlang 和其他语言非常不同的一个地方,如果你在 Erlang 里面使用 “=” 号,那么代表你让 Erlang 计算一个等式或者方程式,Erlang 会尽力给出方程的解,如果方程是无解的,就会抛出 no match 的异常。
X=10.
Erlang 计算这个等式的时候,为了让这个等式成立,所以给 X 绑定了一个值10,作为方程的解。所以 X 的值就变成了10。X=1.
Erlang 计算这个等式,发现 X 为10,等式无法成立,所以抛出异常。
这是非常重要的一个概念,理解了 Erlang 里面的 “=” 的含义后,就能理解了 Erlang 语言中 50% 的内容了。
由于 Erlang 的变量是实际不可变的,所以在进行并行编程的时候,就不会出现多个 Erlang 进程修改同一变量的情况,也就意味着没有竞争,也就没有锁了,这就是为什么用 Erlang 编写并行程序变得很简单的原因之一。
特殊的 Erlang 数据类型
原子
前面说了小写的 x 在 Erlang 中不是一个合法的变量,那么 x 是什么呢?在 Erlang 中,以小写字母开头的元素被称为原子,比如 x、monday、failed都是原子,你可以将原子理解为 Ruby 里的符号,C 和 Java 里面的枚举类型。只不过在 Erlang 中不需要预先定义原子。
元组
元组类似于 C 语言里的结构体,例如{20, 50}
就是一个合法的 Erlang 元组,可以用于表达一个点的坐标。不像 C 语言的结构体,Erlang 元组里面的成员字段是可以没有名字的。通常在 Erlang 里面会用原子来标识元素的含义,增加程序的可读性,比如标识一个三维坐标的元祖可以表示为:
P={{x, 100}, {y, 80}, {z, 170}}.
记录
记录是元组的另外一种形式,它可以用一个名字来标注元组的各个元素,在使用记录前,需要先声明:
-record(record_name, {
field1="default",
field2=1,
field3
})
上面的代码声明了一个名为 record_name 的记录,它包含 3 个字段,其中 field1 和 field 具有一个默认值。 声明一个记录后,就可以创建对应的记录了:
#record_name{field1="new", field3=100}
列表
Erlang 中的列表类似于 Javascript、Ruby 等动态语言中的数组,可以存放任何类型的值,用”[]”表示:
L=[x,20,{a,3.0}].
可以用|来拼接列表:
L1=[100, 200|L].
模式匹配
模式匹配是 Erlang 中的一个关键的概念,就像我在前面说的一样,”=” 在 Erlang 中表达一个等式,其实就是让 Erlang 去执行一个模式匹配,Erlang 会尽力让等式两边的值匹配,以元组{{x, 100}, {y, 80}, {z, 170}}
为例,我们可以写如下等式:
1> {X, {y, Y}, {z, Z}}={{x, 100}, {y, 80}, {z, 170}}.
{{x,100},{y,80},{z,170}}
2> X.
{x,100}
3> Y.
80
4> Z.
170
5>
我们可以看到,为了让等式 {X, {y, Y}, {z, Z}}={{x, 100}, {y, 80}, {z, 170}}
成立,Erlang 将 X 的值绑定为 {x,100},而将 Y 的值绑定为 80,Z 的值绑定为 170,这里相当于用模式匹配获取了元祖各个字段的值。
在编写 Erlang 语言的程序中,大部分工作都是在写模式匹配,能看懂模式匹配,就基本能看懂 Erlang 代码了。
模块与函数
一个 .erl 文件就是模块,在一个模块里可以定义多个函数,并用显示的 export 方式来使方法可以被其他模块使用:
-module(module1).
-export([func/0]).
func()->
...
上面的代码声明了一个名为 module1 的模块,模块里面定义了一个名为 func() 的方法,并将这个方法 export 出去,那么在其他地方调用 func() 方法的方式是:
module1:func().
export 语句中的func/0
函数名后面的数字代表的是函数的参数数量,因为在 Erlang 里面可以有同名的函数,而且同名函数可以拥有一样的参数数量:
func()->
...
func(X,80)->
...
func(X,Y)->
...
有意思的是,Erlang 在决定调用哪个同名函数时,是通过对参数列表进行模式匹配的结果来确定的,以上面的例子为例:func(100, 200) 将调用第三个 func() 函数,而 func(100, 80) 会调用第二个 func() 函数。这样就不用像其他语言一样,在func(X,Y)的函数体里面写if Y==80
这样的分支了。
宏定义
在 Erlang 中可以使用以下方式定义宏:
-define(MACRO_NAME, XXX)
宏定义好之后,可以使用以下方式使用宏:
?MACRO_NAME
OTP
OTP (开发电信平台,Open Telecom Platform),它是 Erlang 的一个框架和库的集合,类似于 Java 的 J2EE 容器,Erlang 程序利用 OTP 比较快速地开发大规模的分布式系统,EMQ X 就是运行在 OTP 上的 Erlang 程序,我们在编写插件的时,也会接触到 OTP 的一些概念。
这一节我们简单介绍了 Erlang 语言的一些特性,这仅仅是 Erlang 的冰山一角,并不能让你学会编写 Erlang 程序,目的是为了便于阅读和理解本课程中的插件代码。下一课,我们就开始编写 RabbitMQ Hook 插件了。