erlang中文基础教程

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

Erlang 编程(第一部分)
1 顺序编程
1.1 The Erlang Shell
大多数操作系统都有一个命令行交互环境或者一个shell,对于UNIX和LINUX尤其是这样。

Windows也有自己的命令行模式。

Erlang也同样有自己的shell,我们可以直接在里面编写代码和运行我们编写的程序,并在其中看到我们的输出情况。

我们能够在多种操作系统上运行Erlang的shell,一般来说在我们所使用的操作系统的命令行中输入erl就可以了,我们将可能看到下面的提示:
% erl
Erlang (BEAM) emulator version 5.2 [source] [hipe]
Eshell V5.2 (abort with ^G)
1>
现在我们输入“2 + 5.”,(注意最后有一个英文句号,并不包括引号哈)
1> 2 + 5.
7
2>
在Windows中,我们还可以通过双击Erlang shell的图标来启动Erlang shell。

你现在注意到了Erlang shell的命令行前面都有一个标号(例如1> 2>),并且下面正确的输出了我们的答案“7”!同样我们注意到我们的输入的最后有一个英文的句号,这是在我们最终按下回车之前的最后一个字符。

如果我们写在shell中的东西有什么错误,我们可以使用退格键进行删除,于是,我们推想我们可以使用其他shell下的一些常用的编辑命令,而事实上确实是这样的,以后我们使用到的时候再介绍。

现在我们尝试一下稍微复杂一点的运算:
2> (42 + 77) * 66 / 3.
2618.00
我们现在使用了一些“复杂一点”的运算符号“*”和“/”,分别表示乘法运算和除法运算。

除此之外还支持其他一些运算符号,我们在以后使用到的时候再介绍。

我们打算关闭Elrang系统,则需要在Erlang shell中键入Ctrl+C,我们将看到下面的输出:
BREAK: (a)bort (c)ontinue (p)roc info (i)nfo (l)oaded
(v)ersion (k)ill (D)b-tables (d)istribution
a
%
我们这时候键入“a”就会退出Erlang系统。

另一个方法就是键入 halt(),也可以实现退出Erlang系统的目的:
3> halt().
%
1.2 Modules 和 Functions (模块和函数)
一个编程语言如果只能让我们从shell中运行代码,那么可以说这个语言的用处受到了很大的限制,至少我会感觉到不爽。

这里有一个小的Erlang程序。

我们将下面的内容键入一个叫做tut.erl的文件(这里需要注意到的是我们的tut.erl文件应该放在erl程序的同一个目录下,文件名应该和模块名相同,这样Erlang才能很好的找到我们的模块,至于编辑器随便一个支持纯文本的就可以哈)。

如果我们的编辑器有一个Erlang模式就可能编写起来更加方便,并且代码的格式也会变得更加规范,但是我们也许也会产生对这些编辑器或者IDE的强烈依赖性。

下面就是我们要键入的代码:
-module(tut).
-export([double/1]).
double(X) ->
2 * X.
不难看出,我们的程序的任务就是将输入的数字进行“乘以2”的操作。

我们后面会解释前两行的含义。

我们现在来编译一下这个程序,我们在Erlang shell中键入下面的内容:
3> c(tut).
{ok,tut}
{ok,tut} 告诉我们编译成功的完成了。

如果显示的是“error”,那么可能我们的文件位置或者键入的代码文本存在某些问题,出错信息会给我们一些提示,我们按照提示做就好了,仔细的检查代码在任何时候都是需要的 : )
现在我们来运行以下这个程序:
4> tut:double(10).
20
和预计的一样,10的“乘以2”之后是20(貌似是废话)。

现在让我们回到最开始的两行代码。

Erlang程序一般是保存在文件中的。

每个文件我们在Erlang中称为module(模块)。

第一行就是告诉我们这个模块的名字。

-module(tut).
这段代码告诉我们该模块的名称为“tut”。

注意这行最后的“.”符号是必不可少的。

这个模块名必须和保存这段代码的文件(后缀为“erl”的文件)有相同的名称。

所以这里我们要求了文件名必须是
“tut.erl”的原因。

当我们在使用另一个模块中的函数时,我们使用下面的语法
module_name:function_name(arguments).所以:
4> tut:double(10).
意味着我们从tut模块中调用一个叫做double的函数,并且带有参数“10”.
第二行:
-export([double/1]).
含一位模块tut中包含一个名叫double的函数,并且带有一个参数(就是例子中的“10”),并且这个函数可以被从模块tut之外调用到。

更深入的内容我们后面会介绍。

现在回头看看上面行尾的句号“.”。

现在有一个稍微复杂一点的例子,是用来进行阶乘(比如4的阶乘就是1*2*3*4的结果)操作的。

键入下面的代码到一个新建的文本文件,并命名为tut1.erl:
-module(tut1).
-export([fac/1]).
fac(1) ->
1;
fac(N) ->
N * fac(N - 1).
编译这个文件:
5> c(tut1).
{ok,tut1}
现在我们计算4的阶乘:
6> tut1:fac(4).
24
第一部分:
fac(1) ->
1;
含义为1的阶乘是1.注意到这部分最后的“;”符号,它预示着下面还有这个函数的其他部分。

第二部分:fac(N) ->
N * fac(N - 1).
含义是N的阶乘等于N乘以N-1的阶乘。

注意这部分最后的符号“.”,含义为对于该函数已经没有更多的内容了,函数就此结束。

一个函数可以有很多的参数。

让我们扩展这个tut1模块,现在多包含一个“白痴函数”:两个数的相乘:-module(tut1).
-export([fac/1, mult/2]).
fac(1) ->
1;
fac(N) ->
N * fac(N - 1).
mult(X, Y) ->
X * Y.
注意到我们也扩展了-export这行,加入了一个包含两个参数的函数mult。

编译一下:
7> c(tut1).
{ok,tut1}
试一下我们的代码是否正常工作了:
8> tut1:mult(3,4).
12
上面的例子使用了整数进行乘法运算,其中N/X/Y成为变量。

注意:变量必须是以大写字母开头,否则编译会提示出错。

下面的变量名就是合法的:Number,ShoeSize,Age等。

1.3 常量
常量是Erlang中的一种数据类型。

常量以小写字符开头,例如:charles、centimeter、inch等。

常量仅仅只是一个简单的名字,不想我们的变量带有自身的值。

键入下面的程序到tut2.erl文件中,该程序帮助我们将英尺转换为厘米:
-module(tut2).
-export([convert/2]).
convert(M, inch) ->
M / 2.54;
convert(N, centimeter) ->
N * 2.54.
编译并测试一下:
9> c(tut2).
{ok,tut2}
10> tut2:convert(3, inch).
1.18110
11> tut2:convert(7, centimeter).
17.7800
注意我们这里使用了十进制的数值(浮点类型),而并没有任何显式的的声明,但是我猜想你们是可以应付这种情况的。

看一下我们键入其他东西会发生什么情况(除了inch和centimeter之外的):
13> tut2:convert(3, miles).
=ERROR REPORT==== 8-Oct-2006::22:52:46 ===
Error in process <0.25.0> with exit value: {function_clause,[{tut2,convert,
[3,miles]},{erl_eval,expr,3},{erl_eval,exprs,4},{shell,eval_loop,2}]}
** exited: {function_clause,[{tut2,convert,[3,miles]},
{erl_eval,expr,3},
{erl_eval,exprs,4},
{shell,eval_loop,2}]} **
这里有两部分被称为子句的内容存在于convert函数中,但是miles并不是这两部分中的其中一部分。

于是Erlang系统不能成功的匹配函数中的子句调用,于是我们就看到了上面的出错提示function_clause 消息。

上面的输出看上去是“典型的一团糟”,但是经过我们认真的观察,我们可以清楚的直到代码中到底是发生了什么。

1.4 元组
现在的tut2程序并不具备一个很好的编程代码的风格。

考虑下面的代码:
tut2:convert(3, inch).
意味着3的单位是inches英尺?还是3是厘米,但是我们打算转换为英尺?所以Erlang有一个方式让这些东西组织为一种更容易理解的形式。

我们称为元组,元组的含义为被“{”和“}”包围着的那部分。

我们可以写{inch,3}来表示3英尺,和{centimeter,5}表示5厘米。

现在让我们重新编写上面的转换程序(文件tut3.erl):
-module(tut3).
-export([convert_length/1]).
convert_length({centimeter, X}) ->
{inch, X / 2.54};
convert_length({inch, Y}) ->
{centimeter, Y * 2.54}.
Compile and test:
14> c(tut3).
{ok,tut3}
15> tut3:convert_length({inch, 5}).
{centimeter,12.7000}
16> tut3:convert_length(tut3:convert_length({inch, 5})).
{inch,5.00000}
注意上面的第16行,我们将5英尺转换为了厘米度量,并将其安全的转换了回去,得到了原来的值。

另外这个例子还说明我们可以将一个函数的返回值作为另一个函数的参数传入。

我们先在第16行这里停一下,考虑一下具体的执行情况。

我们传入了{inch,5}的函数返回的结果成功的匹配了模块中的
convert_length({centimeter,X}),原因在于前一个函数的返回是{centimeter,X}形式的。

如果还不够清楚,那么你可以分别执行这两个函数,仔细看看他们的返回情况。

我们看了有两个部分的元组,但是元组可以有更多的部分组成,我们可以包含任何合法的Erlang内容。

例如,为了表示城市的温度,我们写下如下代码:
{moscow, {c, -10}}
{cape_town, {f, 70}}
{paris, {f, 28}}
元组有固定的内部的组成数量。

我们称在元组中的东西为元素。

所以元组{moscow,{c,-10}},元素1为moscow,元素2为{c,-10}。

这里的c表示摄氏度,f为华氏度。

1.5 列表
元组将各种元素组合在一起,我们同样也希望能够表示一串某些东西。

列表在Erlang中就是被“[”和“]”包围起来的部分。

下面就是一个关于城市和对应气温的列表的例子:
[{moscow, {c, -10}}, {cape_town, {f, 70}}, {stockholm, {c, -4}},
{paris, {f, 28}}, {london, {f, 36}}]
注意到这个列表很长,以致于一行不能显示完,这没有关系,Erlang允许在非敏感的地方换行,但是在一个常量中或者整数中换行就是不允许的。

一个非常有用的寻找列表中一部分的方法就是使用“|”。

在下面的shell中的例子可以很好的进行说明:18> [First |TheRest] = [1,2,3,4,5].
[1,2,3,4,5]
19> First.
1
20> TheRest.
[2,3,4,5]
我们使用“|”来分开第一个列表元素与后面剩下的元素。

(First可以用来获取到值“1”,而TheRest 可以获取到剩下的部分[2,3,4,5])。

另外一个例子:
21> [E1, E2 | R] = [1,2,3,4,5,6,7].
[1,2,3,4,5,6,7]
22> E1.
1
23> E2.
2
24> R.
[3,4,5,6,7]
从上面我们可以看到使用“|”可以从列表中分离最开始的两个元素。

当然,如果我们尝试获取比列表中包含的元素数量更多的元素,我们只会得到一个错误提示。

但是有一个特殊情况就是获得一个没有包含任何元素的空列表[]。

25> [A, B | C] = [1, 2].
[1,2]
26> A.
1
27> B.
2
28> C.
[]
从上面所有的例子中,我们使用了新的变量名,而没有使用已经用过的那些:First、TheRest、E1、E2、R、A、B、C。

原因在于在上下文相关的地方(同一个作用域),我们的变量只能赋值一次。

我们稍后会回到这里,它并不想听上去那么奇怪。

下面的例子展示了我们如何得到一个列表的长度:
-module(tut4).
-export([list_length/1]).
list_length([]) ->
0;
list_length([First | Rest]) ->
1 + list_length(Rest).
编译文件“tut4.erl”并且运行一下:
29> c(tut4).
{ok,tut4}
30> tut4:list_length([1,2,3,4,5,6,7]).
7
解释下面的语句:
list_length([]) ->
0;
表明了空列表的长度被我们定义为0。

list_length([First | Rest]) ->
1 + list_length(Rest).
上面的代码表明一个非空列表的长度是第一个元素First加上后面剩下的Rest部分的长度的和。

(对于高级一点的读者:这里并非只能使用递归的方式,也有一些更好的方式来实现哈。


一般情况下我们可以在我们在其他语言中使用“记录records”或者“结构structs”的地方说我们使用了列表,特别是我们试图表现一些可变大小的数据时。

(就如同我们在其他语言中使用链表一样)Erlang没有一个字符串数据类型,取而代之的是使用ASCII表示的列表。

所以列表[97,98,99]等效为字符串“abc”。

Erlang Shell是一个聪明的系统,可以猜出我们的列表到底想要表达什么样的数据,并且以合适的方式进行输出,这在大多数场合都是适用的。

例如:
31> [97,98,99].
"abc"
1.6 标准模块和手册页面
Erlang有很多标准模块帮助我们做一些常见的事情。

例如,模块io包含了很多函数帮助我们格式化输入和输出。

搜寻这些标准模块的信息,我们可以使用命令erl-man(可以是操作系统的提示符或者Erlang Shell的提示符都可以),下面是在操作系统的提示符下进行:
% erl -man io
ERLANG MODULE DEFINITION io(3)
MODULE
io - Standard I/O Server Interface Functions
DESCRIPTION
This module provides an interface to standard Erlang IO
servers. The output functions all return ok if they are suc-
...
如果在你的操作系统上并不支持这个特性,那么就看Erlang/OTP发行版本中的文档吧,或者是从
www.erlang.se网站上下载文档(html或者pdf格式的)或者是上面也有。

下面是R98
的文档地址:
/doc/r9b/doc/index.html
1.7 输出到终端
在下面的例子中我们可以很好的将格式化的结果输出到终端,我们将从中学习如何使用io:format函数。

当然,和其他很多函数一样,我们可以在shell中测试这些函数的实际效果:
32> io:format("hello world~n", []).
hello world
ok
33> io:format("this outputs one Erlang term: ~w~n", [hello]).
this outputs one Erlang term: hello
ok
34> io:format("this outputs two Erlang terms: ~w~w~n", [hello, world]).
this outputs two Erlang terms: helloworld
ok
35> io:format("this outputs two Erlang terms: ~w ~w~n", [hello, world]).
this outputs two Erlang terms: hello world
ok
函数format/2(一个函数format带有两个参数)需要两个列表作为输入。

这第一个列表总是在" "之间的。

这个列表是输出的基准串,除了里面的~w将被替换为后面的第二个列表中对应位置的内容。

每个~n将被替换为一个回车(或者理解为替换为新的一行)。

io:fomrat/2函数如果运行一切正常的话,自己返回一个常量ok。

如同其他Erlang中的函数一样,如果发生什么错误将会直接提示出错信息。

这并不是Erlang 的错误或者缺陷,只是一个经过深思熟虑的策略。

Erlang有一个经过长期检验的实现机制来捕获错误,我们稍后会深入的讨论相关的内容。

作为一个联系,我们尝试让io:format挂掉,这应该不难,在这个过程中Erlnag本身是不会挂掉的。

1.8 一个大一些的例子
现在有一个大一些的例子帮助我们更加深入的学习。

这里我们准备了一个从一组城市中读取气温的列表。

其中一些是摄氏度,另一些是华氏度的表示,让我们以更加适于阅读的方式输出这些信息:
%% This module is in file tut5.erl
-module(tut5).
-export([format_temps/1]).
%% Only this function is exported
format_temps([])-> % No output for an empty list
ok;
format_temps([City | Rest]) ->
print_temp(convert_to_celsius(City)),
format_temps(Rest).
convert_to_celsius({Name, {c, Temp}}) -> % No conversion needed
{Name, {c, Temp}};
convert_to_celsius({Name, {f, Temp}}) -> % Do the conversion
{Name, {c, (Temp - 32) * 5 / 9}}.
print_temp({Name, {c, Temp}}) ->
io:format("~-15w ~w c~n", [Name, Temp]).
36> c(tut5).
{ok,tut5}
37> tut5:format_temps([{moscow, {c, -10}}, {cape_town, {f, 70}},
{stockholm, {c, -4}}, {paris, {f, 28}}, {london, {f, 36}}]).
moscow -10 c
cape_town 21.1111 c
stockholm -4 c
paris -2.22222 c
london 2.22222 c
ok
在我们关注程序怎样运行之前,注意我们在代码中添加了一些注释信息。

注释都是以%开头的。

注意
-export([format_temps/1]).这行意味着包含一个函数format_temps/1,其他函数都是本地函数,也就是说在模块tut5之外是看不到这些函数的。

同样注意我们在shell中运行这个程序的情况,我们的输入跨越了两行,因为太长了,这也是允许的。

当调用首次format_temps的时候,City获得值{moscow,{c,-10}},并且Rest保存着剩余的列表。

然后我们调用函数print_temp(covert_to_celsius({moscow,{c,-10}}))。

convert_to_celsius({moscow,{c,-10}})是作为print_temps的参数。

这个嵌套的函数的执行是从内到外的一个过程。

首先我们执行convert_to_celsius({moscow,{c,-10}}),因为我们传入的参数已经是使用摄氏度的表达形式了,所以下面我们马上紧接着执行print_temp({moscow,{c,-10}})。

函数
convert_to_celsius的工作情况与前面的例子convert_length很类似。

print_temp函数简单的调用了io:format函数。

这里的~-15w表达的意思是打印传入的值,但是限定长度(或者说是数据宽度)必须小于15,数据左对齐。

现在我们调用format_temps(Rest),将剩余的部分作为参数传入。

这个工作的方式很类似于其他语言中的循环结构。

(这里是一个递归的过程,不用太“害怕”哈)。

同样的,format_temps函数被再次调用,这次City得到值{cape_town,{f,70}},我们重复上面说过的过程进行数据的处理。

我们持续的运行该程序,直到列表为空。

当列表为空时,执行第一个子句format_temps([]),程序简单的返回一个常量ok,至此,程序顺利结束。

1.9 变量的匹配、限定和作用域
我们可能需要寻找在列表中的最高和最低温度。

在我们动手扩展我们前面的程序之前,让我们来看一个在列表中寻找最大值的元素的函数:
-module(tut6).
-export([list_max/1]).
list_max([Head|Rest]) ->
list_max(Rest, Head).
list_max([], Res) ->
Res;
list_max([Head|Rest], Result_so_far) when Head > Result_so_far ->
list_max(Rest, Head);
list_max([Head|Rest], Result_so_far) ->
list_max(Rest, Result_so_far).
39> c(tut6).
{ok,tut6}
40> tut6:list_max([1,2,3,4,5,7,4,3,2,1]).
7
首先注意到这里有两个相同名称的函数list_max。

尽管这些函数有不同数量的参数。

在Erlang中它们被处理为完全不同的函数。

但我们需要根据name/arity来分辨不同的函数,name是函数的名称,arity是参数的个数,对于上面的例子分别是list_max/1和list_max/2。

上面的例子接受一个我们构造的列表,我们使用Result_so_far“搬移”一个列表中的值。

list_max/1简单的假设列表中最大值是列表的头部元素,并给调用list_max/2来处理剩下的元素来与头部元素进行对比,在上面代码就将是list_max([2,3,4,5,7,4,3,2,1],1)。

如果我们尝试使用list_max/1来处理一个空列表,我们将得到一个错误提示。

注意这个Erlang体系不能够捕获这种类型的函数错误,但是会有其他的办法来处理,稍后会有详细的讨论。

在list_max/2中我们继续“走完”声誉的列表,当Head>Result_so_far条件满足的时候使用
Result_so_far替代原先的Head的值。

->前面的when是一个关键词,告诉我们如果条件为真则执行函数的这一部分,我们将这种测试过程称为界定(guard)。

如果一个条件界定不为真(也就是说界定失败),我们尝试运行函数的另一部分。

在这个例子中如果Head不是大于Result_so_far,也就是Head等于或者小于Resutl_so_far,所以我们就不需要在函数的另一部分再加入一个界定来判断执行条件了。

一些有用的界定符例如“<小于”,“>大于”,“==等于”,“>=不小于”,“<=不大于”,“/=不等于”。

改变上面的程序,使其变为寻找列表中最小值的函数,我们只需要将<替换为>符号就可以了。

(除此之外似乎还应该将函数名称改为list_min更为恰当 :))
记得我曾经在较早前提起过变量只能在作用域内被赋值一次吗?在上面我们看到,Result_so_far被重复的赋值了很多次,这是合法的,因为每次我们调用list_max/2的时候系统都会新建一个作用域并且每个作用域内的变量都是完全不一样的。

另一种创建和赋值给变量的方法是使用操作符=。

所以如果我写下M=5,一个叫做M的变量就会被创建然后被赋值为5.如果我又在同一个作用域中写M=6,我们将得到一个错误提示。

在shell中尝试下面的代码:41> M = 5.
5
42> M = 6.
** exited: {{badmatch,6},[{erl_eval,expr,3}]} **
43> M = M + 1.
** exited: {{badmatch,6},[{erl_eval,expr,3}]} **
44> N = M + 1.
6
匹配操作符在分离和创建新的变量方面有独特的作用:
45> {X, Y} = {paris, {f, 28}}.
{paris,{f,28}}
46> X.
paris
47> Y.
{f,28}
这里我们的X得到了值pairs,Y得到了{f,28}。

当然,如果我们尝试对其他的城市重复上面的操作又不改变变量,那么会得到出错信息:
49> {X, Y} = {london, {f, 36}}.
** exited: {{badmatch,{london,{f,36}}},[{erl_eval,expr,3}]} **
变量能够用来提高程序的可阅读性,例如,上面的list_max/2函数,我们可以写:
list_max([Head|Rest], Result_so_far) when Head > Result_so_far ->
New_result_far = Head,
list_max(Rest, New_result_far);
这样的写法可能更加清晰一点。

1.10 进一步讨论列表
记得“|”操作符在获取列表头部的作用吗?
50> [M1|T1] = [paris, london, rome].
[paris,london,rome]
51> M1.
paris
52> T1.
[london,rome]
“|”操作符也可以用来为列表增加一个头元素:
53> L1 = [madrid | T1].
[madrid,london,rome]
54> L1.
[madrid,london,rome]
下面我们尝试将列表进行翻转操作:
-module(tut8).
-export([reverse/1]).
reverse(List) ->
reverse(List, []).
reverse([Head | Rest], Reversed_List) ->
reverse(Rest, [Head | Reversed_List]);
reverse([], Reversed_List) ->
Reversed_List.
56> c(tut8).
{ok,tut8}
57> tut8:reverse([1,2,3]).
[3,2,1]
考虑Reversed_List是如何被构造出来的。

首先是从一个空列表[]开始,我们首先获取现有列表的头元素,放入Reversed_List中,具体过程显示如下:
reverse([1|2,3], []) =>
reverse([2,3], [1|[]])
reverse([2|3], [1]) =>
reverse([3], [2|[1])
reverse([3|[]], [2,1]) =>
reverse([], [3|[2,1]])
reverse([], [3,2,1]) =>
[3,2,1]
模块lists包含了很多对列表进行操作的函数,例如翻转列表,在我们自己动手写一个函数之前,最好还是首先检查一下有没有在模块中已经为我们准备好的函数。

现在我们会过头来看看城市和温度的问题,但是这次将更加结构化一点。

首先让我们把整个列表转化为摄氏度表示,并且测试下面的函数:
-module(tut7).
-export([format_temps/1]).
format_temps(List_of_cities) ->
convert_list_to_c(List_of_cities).
convert_list_to_c([{Name, {f, F}} | Rest]) ->
Converted_City = {Name, {c, (F -32)* 5 / 9}},
[Converted_City | convert_list_to_c(Rest)];
convert_list_to_c([City | Rest]) ->
[City | convert_list_to_c(Rest)];
convert_list_to_c([]) ->
[].
58> c(tut7).
{ok, tut7}.
59> tut7:format_temps([{moscow, {c, -10}}, {cape_town, {f, 70}},
{stockholm, {c, -4}}, {paris, {f, 28}}, {london, {f, 36}}]).
[{moscow,{c,-10}},
{cape_town,{c,21.1111}},
{stockholm,{c,-4}},
{paris,{c,-2.22222}},
{london,{c,2.22222}}]
我们一点一点的看:
format_temps(List_of_cities) ->
convert_list_to_c(List_of_cities).
这里我们看到format_temps/1调用了convert_list_to_c/1。

convert_list_to_c/1获得列表
List_of_cities的头元素,如果需要的话就进行摄氏度的转换。

“|”操作符被用来添加转化后的元素到已转化的列表部分。

[Converted_City | convert_list_to_c(Rest)];
或者
[City | convert_list_to_c(Rest)];
我们继续这样的操作直到获取了列表中的最后一个元素(也就是到列表为空)
convert_list_to_c([]) ->
[].
现在我们就完成了对列表的转换工作,我们添加一个函数并且输出它:
-module(tut7).
-export([format_temps/1]).
format_temps(List_of_cities) ->
Converted_List = convert_list_to_c(List_of_cities),
print_temp(Converted_List).
convert_list_to_c([{Name, {f, F}} | Rest]) ->
Converted_City = {Name, {c, (F -32)* 5 / 9}},
[Converted_City | convert_list_to_c(Rest)];
convert_list_to_c([City | Rest]) ->
[City | convert_list_to_c(Rest)];
convert_list_to_c([]) ->
[].
print_temp([{Name, {c, Temp}} | Rest]) ->
io:format("~-15w ~w c~n", [Name, Temp]),
print_temp(Rest);
print_temp([]) ->
ok.
60> c(tut7).
{ok,tut7}
61> tut7:format_temps([{moscow, {c, -10}}, {cape_town, {f, 70}},
{stockholm, {c, -4}}, {paris, {f, 28}}, {london, {f, 36}}]).
moscow -10 c
cape_town 21.1111 c
stockholm -4 c
paris -2.22222 c
london 2.22222 c
ok
我们现在已经添加了一个函数去寻找有最高气温和最低气温的城市了。

这个程序并不是最有效率的,我们4次遍历整个列表,但是对于清晰程度和正确性来说却没有太大的影响,我们只在确实需要优化性能的时候进行优化:
-module(tut7).
-export([format_temps/1]).
format_temps(List_of_cities) ->
Converted_List = convert_list_to_c(List_of_cities),
print_temp(Converted_List),
{Max_city, Min_city} = find_max_and_min(Converted_List),
print_max_and_min(Max_city, Min_city).
convert_list_to_c([{Name, {f, Temp}} | Rest]) ->
Converted_City = {Name, {c, (Temp -32)* 5 / 9}},
[Converted_City | convert_list_to_c(Rest)];
convert_list_to_c([City | Rest]) ->
[City | convert_list_to_c(Rest)];
convert_list_to_c([]) ->
[].
print_temp([{Name, {c, Temp}} | Rest]) ->
io:format("~-15w ~w c~n", [Name, Temp]),
print_temp(Rest);
print_temp([]) ->
ok.
find_max_and_min([City | Rest]) ->
find_max_and_min(Rest, City, City).
find_max_and_min([{Name, {c, Temp}} | Rest],
{Max_Name, {c, Max_Temp}},
{Min_Name, {c, Min_Temp}}) ->
if
Temp > Max_Temp ->
Max_City = {Name, {c, Temp}}; % Change
true ->
Max_City = {Max_Name, {c, Max_Temp}} % Unchanged
end,
if
Temp < Min_Temp ->
Min_City = {Name, {c, Temp}}; % Change
true ->
Min_City = {Min_Name, {c, Min_Temp}} % Unchanged
end,
find_max_and_min(Rest, Max_City, Min_City);
find_max_and_min([], Max_City, Min_City) ->
{Max_City, Min_City}.
print_max_and_min({Max_name, {c, Max_temp}}, {Min_name, {c, Min_temp}}) ->
io:format("Max temperature was ~w c in ~w~n", [Max_temp, Max_name]),
63> tut7:format_temps([{moscow, {c, -10}}, {cape_town, {f, 70}},
{stockholm, {c, -4}}, {paris, {f, 28}}, {london, {f, 36}}]).
moscow -10 c
cape_town 21.1111 c
stockholm -4 c
paris -2.22222 c
london 2.22222 c
Max temperature was 21.1111 c in cape_town
Min temperature was -10 c in moscow
ok
1.11 If 和 Case 语句
函数find_max_and_min将为我们找到最高和最低气温。

我们在这里引入了一个新的关键字if,它的工作情况如下:
if
Condition 1 ->
Action 1;
Condition 2 ->
Action 2;
Condition 3 ->
Action 3;
Condition 4 ->
Action 4
end
注意,在end前面的最后一个条件是没有“;”的!这里的判定条件和界定(Guard)是一样的,测试条件的真或假。

Erlang从最高处开始执行,直到它找到一个为真的条件,并执行其内部的代码,并且很重要的是它将忽略其他剩下的条件,不论其他剩下的条件中是否还有为真的情况。

一个条件当是常量的时候意味着永远为真,true和常量(atoms)常常用来作为if的最后一个条件。

作为当其他所有条件都为假时的执行出口。

下面是一个简短的程序,用来表现if工作的情况:
-module(tut9).
-export([test_if/2]).
test_if(A, B) ->
if
A == 5 ->
io:format("A = 5~n", []),
a_equals_5;
B == 6 ->
io:format("B = 6~n", []),
b_equals_6;
A == 2,
B == 3 -> %i.e. A equals 2 and B equals 3
io:format("A == 2, B == 3~n", []),
a_equals_2_b_equals_3;
A == 1 ;
B == 7 -> %i.e. A equals 1 or B equals 7
io:format("A == 1 ; B == 7~n", []),
a_equals_1_or_b_equals_7
end.
65> tut9:test_if(5,33).
A = 5
a_equals_5
66> tut9:test_if(33,6).
B = 6
b_equals_6
67> tut9:test_if(2, 3).
A == 2,
B == 3
a_equals_2_b_equals_3
68> tut9:test_if(1, 33).
A == 1 ;
B == 7
a_equals_1_or_b_equals_7
69> tut9:test_if(33, 7).
A == 1 ;
B == 7
a_equals_1_or_b_equals_7
70> tut9:test_if(33, 33).
=ERROR REPORT==== 11-Jun-2003::14:03:43 ===
Error in process <0.85.0> with exit value:
{if_clause,[{tut9,test_if,2},{erl_eval,exprs,4},{shell,eval_loop,2}]}
** exited: {if_clause,[{tut9,test_if,2},
{erl_eval,exprs,4},
{shell,eval_loop,2}]} **
注意到执行tut9:test_if(33,33)时由于没有任何条件可以满足时出现了错误if_clause。

case是另外一种Erlang中的判断结构。

回忆我们以前写的convert_length函数:
convert_length({centimeter, X}) ->
{inch, X / 2.54};
convert_length({inch, Y}) ->
{centimeter, Y * 2.54}.
我们可以改写为:
-module(tut10).
-export([convert_length/1]).
convert_length(Length) ->
case Length of
{centimeter, X} ->
{inch, X / 2.54};
{inch, Y} ->
{centimeter, Y * 2.54}
end.
71> c(tut10).
{ok,tut10}
72> tut10:convert_length({inch, 6}).
{centimeter,15.2400}
73> tut10:convert_length({centimeter, 2.5}).
{inch,0.98425}
-module(tut11).
-export([month_length/2]).
month_length(Year, Month) ->
%% All years divisible by 400 are leap
%% Years divisible by 100 are not leap (except the 400 rule above)
%% Years divisible by 4 are leap (except the 100 rule above)
Leap = if
trunc(Year / 400) * 400 == Year ->
leap;
trunc(Year / 100) * 100 == Year ->
not_leap;
trunc(Year / 4) * 4 == Year ->
leap;
true ->
not_leap
end,
case Month of
sep -> 30;
apr -> 30;
jun -> 30;
nov -> 30;
feb when Leap == leap -> 29;
feb -> 28;
jan -> 31;
mar -> 31;
may -> 31;
jul -> 31;
aug -> 31;
oct -> 31;
dec -> 31
end.
74> c(tut11).
{ok,tut11}
75> tut11:month_length(2004, feb).
29
76> tut11:month_length(2003, feb).
28
77> tut11:month_length(1947, aug).
31
1.12 内建函数BIFs
内建函数Bifs是一些处于某些理由构建在Erlang虚拟机内部的函数。

BIFs常常实现功能性的操作,而这些操作可能是很难在Erlang中直接实现的,或者说是实现起来没有效率的。

一些BIFs可以被通过函数名进行调用,它们这时是默认属于Erlang模块的,例如上面看到的trunc函数其实是erlang:trunc。

如你所见,我们首先找出某一年是否是闰年。

如果某一年可以被400整除,则是闰年。

为了找到能被400整除的年份,我们使用了内建函数trunc来将小数部分切割掉。

我们然后再乘上400,看看是否可以恢复原来的数值,例如,对于2004年来说:
我们看到得到的是2000而不是2004,所以我们知道了2004并不能被400整除。

再看看2000年:
2000 / 400 = 5.0
trunc(5.0) = 5
5 * 400 = 2000
于是,这就的到一个闰年了。

接下来的两个测试是如果可以被100或者4整除,也是闰年,实现的过程很类似。

第一个if返回leap或者not_leap(当时闰年的时候返回leap)。

我们使用这个变量来界定二月份的日期长度情况。

这个例子展示了如何使用trunc函数,我们使用另外一个操作符rem能够轻松的得到余数,看例子:
2> 2004 rem 400.
4
我们写的是:
trunc(Year / 400) * 400 == Year ->
leap;
改写为:
Year rem 400 == 0 ->
leap;
这里有很多的内建函数BIFs,但是只有一些BIFs可以作为界定来使用,并且你不能使用自定义的函数作为界定。

(对于高级一点的读者:这里需要注意界定是没有副作用的)。

让我们看看这些BIFs是怎样的:78> trunc(5.6).
5
79> round(5.6).
6
80> length([a,b,c,d]).
4
81> float(5).
5.00000
82> is_atom(hello).
true
83> is_atom("hello").
false
84> is_tuple({paris, {c, 30}}).
true
85> is_tuple([paris, {c, 30}]).
false
上面的所有BIFs都可以作为界定。

而下面的这些则不行:
87> atom_to_list(hello).
"hello"
88> list_to_atom("goodbye").
goodbye
89> integer_to_list(22).
"22"
上面的3个BIFs可以帮助我们完成一些在Erlang中很困难甚至是不可能的任务。

1.13 更高端的函数(Funs)
Erlang和其他的函数式编程语言一样,有一些高端的函数,我们下面就来看看这部分的内容:
90> Xf = fun(X) -> X * 2 end.
#Fun<erl_eval.5.123085357>
我们在这里定义了一个函数,其功能是将输入的数值乘以2.于是我们调用Xf(5)得到结果10.两个在日常工作中有用的函数是foreach和map,定义如下:
foreach(Fun, [First|Rest]) ->
Fun(First),
foreach(Fun, Rest);
foreach(Fun, []) ->
ok.
map(Fun, [First|Rest]) ->
[Fun(First)|map(Fun,Rest)];
map(Fun, []) ->
[].
这两个函数都在模块lists中。

foreach需要一个列表作为输入,然后对每个列表元素应用一次fun函数。

而map则创建一个新的列表来保存被fun函数作用过的列表元素。

回到shell中,我们使用map和fun对列表中的每个元素都加上3:
92> Add_3 = fun(X) -> X + 3 end.
#Fun<erl_eval.5.123085357>
93> lists:map(Add_3, [1,2,3]).
[4,5,6]
现在让我们输出城市的温度列表:
95> Print_City = fun({City, {X, Temp}}) -> io:format("~-15w ~w ~w~n",
[City, X, Temp]) end.
#Fun<erl_eval.5.123085357>
96> lists:foreach(Print_City, [{moscow, {c, -10}}, {cape_town, {f, 70}},
{stockholm, {c, -4}}, {paris, {f, 28}}, {london, {f, 36}}]).
moscow c -10
cape_town f 70
stockholm c -4
paris f 28
london f 36
ok
我们现在定义一个fun函数,来将列表中的华氏度全部转换为摄氏度:
-module(tut13).
-export([convert_list_to_c/1]).
convert_to_c({Name, {f, Temp}}) ->
{Name, {c, trunc((Temp - 32) * 5 / 9)}};
convert_to_c({Name, {c, Temp}}) ->
{Name, {c, Temp}}.
convert_list_to_c(List) ->
lists:map(fun convert_to_c/1, List).
98> tut13:convert_list_to_c([{moscow, {c, -10}}, {cape_town, {f, 70}},
{stockholm, {c, -4}}, {paris, {f, 28}}, {london, {f, 36}}]).
[{moscow,{c,-10}},
{cape_town,{c,21}},
{stockholm,{c,-4}},
{paris,{c,-2}},
{london,{c,2}}]
但我们使用一个某处定义的函数作为fun时,我们应该明确的知道它的方法名和参数数量
(Function/Arity)。

所以在map中我们写lists:map(fun convert_to_c/1,List)。

所以你可以看到convert_list_to_c变短了,变得更加容易阅读理解了。

标准模块lists同样包含了函数sort(Fun,List),这里的fun带有两个参数。

如果第一个参数小于第二个参数则fun应该返回true,否则应该返回false。

我们将其添加到convert_list_to_c中:
-module(tut13).
-export([convert_list_to_c/1]).
convert_to_c({Name, {f, Temp}}) ->
{Name, {c, trunc((Temp - 32) * 5 / 9)}};
convert_to_c({Name, {c, Temp}}) ->
{Name, {c, Temp}}.
convert_list_to_c(List) ->
New_list = lists:map(fun convert_to_c/1, List),
lists:sort(fun({_, {c, Temp1}}, {_, {c, Temp2}}) ->
Temp1 < Temp2 end, New_list).
99> c(tut13).
{ok,tut13}
100> tut13:convert_list_to_c([{moscow, {c, -10}}, {cape_town, {f, 70}},
{stockholm, {c, -4}}, {paris, {f, 28}}, {london, {f, 36}}]).
[{moscow,{c,-10}},
{stockholm,{c,-4}},
{paris,{c,-2}},
{london,{c,2}},
{cape_town,{c,21}}]
在sort中我们使用fun:
fun({_, {c, Temp1}}, {_, {c, Temp2}}) -> Temp1 < Temp2 end,
这里我们引入了一个概念——匿名变量"_"(anonymous variable)。

这是一种当获取一个值时的缩写的形式,但是我们将忽略这个值。

我们可以在任何地方使用这个匿名特性,不仅仅是在fun中。

Temp1<Temp2返回true,如果Temp1小于Temp2的话。

2 并发编程
2.1 进程
使用Erlang而不是其他函数式语言的一个很主要的原因就是Erlang具有处理并发和分布式计算的编程能力。

我们这里说的并发是指程序可以在同一个时点处理多个线程的执行。

例如,现代操作系统可以允许你使用Word的同时使用Excel,并且还开着一个电子邮件客户端程序,一个打印的任务在后台也在执行着。

当然,对于系统中的每个处理器(CPU)来说同一时刻只能处理一个线程(任务),但是当系统以一定的速率在不同的线程之间进行切换的时候,给人类的感觉就是这些任务都是在同一时间执行的。

在一个Erlang程序中很容易创建一个线程进行并发、并行的执行操作,线程之间的通讯也是非常容易的。

在Erlang 系统中,我们称每一个执行的线程为Process(注意这里的特殊性,不要与其他系统中的进程相混淆)。

(注意:专有名词“Process”经常用于当执行的线程不与别的线程进行数据共享的情况对这种线程的称呼。

当这些线程之间共享数据时,我们一般把它们看作一个整体,作为一个Process进程。

在Erlang中,我们往往称呼不共享数据的Thread为Process,并且很多时候是混合着叫的,读者应该自己从上下文中进行分辨)
Erlang的内建函数spawn被用来创建一个新的进程:spawn(Module,Exported_Function,List of Arguments)。

考虑下面的例子:
-module(tut14).
-export([start/0, say_something/2]).。

相关文档
最新文档