Erlang顺序编程

Erlang拥有条件评估计算形式,它们是可以互换的:

1.通过函数的参数进行模式匹配来进行函数的选择执行。

2.case结构。

2.1.case结构依靠模式匹配来判断应该执行那些语句,它跟通过模式匹配来选择执行函数很类似。不同的是,case不是把函数的真实参数和形式参数进行模式匹配,而是通过一个表达式求值,然后对它的结果和一个由分号隔开的模式匹配列表进行匹配。

2.2.一般情况下,case表达式有如下形式:

case conditional-expression of

Pattern1->expression1,expression2,...;

Pattern2->expression1,expression2,...;

...;

Patternn->expression1,expression2,...;

end;

使用的关键字是case、of和end,对conditional-expression求值,然后和Pattern1,...,Patternn进行匹配,直到第一个模式匹配成功。->符号把语句的模式或者头部与由逗号分隔的表达式列表组成的主体分开,一旦模式匹配成功,选中的语句表达式就会一个个按次序执行,case结构的结果就是最后一个表达式的运行结果。

2.3.case表达式总是返回一个值,因此总是可以把它的返回值绑定到一个变量。

2.4.case表达式的结果必须和一个模式匹配,否则将会得到一个运行时错误。如果在最后的模式中有_或者未绑定变量,那么它将匹配任何Erlang项元。

2.5.带一个参数的函数可以直接使用case来重写;带多个参数的函数可以同时对所有的参数进行模式匹配;一个case表达式可以匹配单一的表达式,可以使用用多个参数构造的元组;通过嵌套的case表达式,模式匹配可以匹配两个参数。

2.6.使用模式匹配定义函数要比使用case表达式代码更紧凑,但是要记得case表达式不单单用于头部,它可以用于函数定义的任何地方。

3.if结构。

3.1.if结构就像一个没有condition-expression和of关键字的case语句:

if

Guard1->expression1,expression2,...;

Guard2->expression1,expression2,...;

...;

Guardn->expression1,expression2,...;

end

保护元表达式Guard1,Guard2,...,Guardn按次序进行计算,直到其中的一个计算结果为true。

3.2.保护元表达式是Erlang中布尔表达式语句的子集,它只能调用有限的包含比较运算符和算术运算符的函数。

3.3.如果没有保护元语句的计算值是基元true,那么运行时就会产生错误。要得到一个catch-all的语句,可以让最后一个保护元语句的值是基元true,当然这不是强制性的。

变量范围

1.变量的范围是指程序中变量可以使用的区域。

2.Erlang中一个变量的范围是在相同函数中变量被绑定后的任意位置。

保护元

1.保护元是一个额外的限制条件,它应用于函数的case或者receive语句中。保护元应该放在“->”之前来分割语句的主体和头部。

2.保护元由when关键字和紧跟其后的一个保护元表达式组成。只有在模式匹配和保护元表达式求值结果为基元true的情况下,这个语句才会执行。

3.如果是模式匹配和保护元一起来唯一确定哪些语句应该选中,那么这些语句的顺序就变得无关紧要了。

4.单独的保护元表达式可以使用如下的结构获得:

约束变量

Erlang常量的数据值,包括数字,基元,元组和列表等。

类型测试语句,比如is_binary,is_atom,is_boolean和is_tuple等。

第二章中所列出的项元比较运算符==、=/=、<、>等。

第二章中所列出的使用算术运算符组成的算术表达式。

第二章中所列出的布尔表达式。

保护元内置函数。

导致运行时错误的保护元子表达式被视为返回false。

5.规定开发人员不能用语句实现自己的保护元函数的原因是要限制它们的操作,从而确保保护元语句不会产生边界效应。在找到一个成功的语句执行之前,将会执行所有的测试保护元语句,这意味着假设在一个保护元里调用io:format,如果它失败了,它还是会看到打印输出,即使这个语句没有被选中执行。

6.Erlang允许保护元进行简单的逻辑组合,以不同的方式实现:

用逗号(,)来分割各个保护元语句,这是一种逻辑乘,因为只有在串行序列中所有表达式的值都是true的时候,它的结果才为true。

用分号(;)来分割各个保护元语句,这是一种逻辑加,如果有一个表达式的值为true,则它的结果就为true。

7.只使用逗号或者分号的简单组合是优美的,在实践中我们不建议把逗号和分号混合使用,因为这样太容易产生逻辑错误了。

内置函数

1.内置函数的简写是BIF.

2.内置函数通常用c语言编写,并集成到虚拟机(VM)中,可用于操作、检查并获取数据,以及与操作系统进行交互。

3.最初,所有的内置函数都属于erlang模块,目前在现实中因为考虑实用性和有效性有一些也在其它模块中实现。其中包含有内置函数的模块还有ets和lists。

4.虽然大多数内置函数视为Erlang的内部组成部分,但是其他一些则要看不同的虚拟机实现,它们并不一定存在于其他虚拟机实现中,或者现有虚拟机的特定操作系统版本中。标准的内置函数会自动包括进来,因此,调用它们不需要添加模块前缀。但是非标准的内置函数,必须加上erlang模块前缀,如erlang:function。

内置函数-对象的存取与检查

1.hd/1:返回列表的第一个元素。

2.tl/1:返回删除第一个元素后的其余部分。

3.length/1:返回一个列表的长度。

4.tuple_size/1:返回元组元素的数目。

5.element/2:返回元组的第n个元素。

6.selelement/3:替换元组中的一个元素,并返回新的元组。

7.erlang:append_element/2:向元组添加一个元素作为最后的元素。

内置函数-类型转换

1.类型转换必须是内置函数,因为它们改变了数据的内在表示方法。即使可行,但也不可能用Erlang语言编写出有效率的转换函数。类型转换函数有许多,它们不仅改变数值类型,而且同时也可以在基础类型和可打印类型(即字符串)之间相互转换。

2.atom_to_list/1,list_to_atom/1,list_to_existing_atom/1:它们实现了基元和字符串的相互转换,如果基元在运行时系统中的当前会话里没有使用过,那么调用函数list_to_existing_atom/1将会失败。

3.list_to_tuple/1,tuple_to_list/1:这两个函数实现元组类型和列表类型的相互转换。

4.float/1,list_to_float/1:这两个函数都产生一个float类型,一个是把一个整数参数转换成一个浮点数,另外一个是把一个字符串转换成一个浮点数。

5.float_to_list/1,integer_to_list/1:这两个函数都返回字符串。

6.round/1,trunc/1,list_to_integer/1,它们都返回整数。

内置函数-进程字典

1.有一类内置函数可以允许函数把值和一个键关联起来并存储,以后可以在程序的其它地方重新获取它,它们成为进程字典。遗憾的是,检索和操纵这些值将引入Erlang中的全局变量。

2.使用进程字典可以在编程的时候提供一个快速的方法,但结果是代码非常难以调试和维护。大多数的Erlang函数是没有边界效应的,当程序崩溃的时候,传递给函数的参数通常包含了足够的信息来解决错误。引入进程字典后往往使这个过程大大复杂化,因为当程序崩溃的时候,进程字典的状态值也同时丢失了。

内置函数-元编程

1.元编程是指某类计算机程序的编写,这类计算机程序编写或者操纵其它程序(或者自身)作为它们的数据,或者在运行时完成部分本应在编译时完成的工作。一个函数在运行时才确定将调用哪些函数的特性叫做元编程,也就是程序创建程序并运行。

2.可以使用带三个参数的apply/3函数来实现,即模块名称,导出的函数名和参数列表。当调用时,它执行以参数给出名称的函数并返回其结果。

3.apply/3的美妙之处在于模块,函数和参数无须在编译时就已知。

内置函数-进程、端口、发布和系统信息

1.date/0:以元组{Year,Month,Day}的形式返回当前日期。

2.time/0:以元组{Hour,Minute,Second}的形式返回当前时间。

3.now/0:以元组{MegaSeconds,Seconds,MicroSeconds}的形式返回一个从1970年1月1号午夜算起的秒数。

4.now/1:会返回在特定的Erlang节点的唯一值,即使它们在同一微妙里被多次调用,因此它可以用作一个唯一的标识符。

内置函数-输入和输出

1.io模块提供了Erlang程序的输入和输出功能。它包括从标准输入设备读取和在标准输出设备中输出等主要功能。每个函数都可以接受文件句柄(属于类型io_device())作为额外的第一参数:在file模块中定义文件操作。

2.要从标准输入中读取一行,有三种方法:

2.1.使用io:get_line/1,它需要提示符字符串(或者基元)作为它的输入:

io:get_line("gissa_line>").

gissa_line>lksd.

"lksd.\n"

2.2.使用io:get_chars/2来读取指定数量的字符:

io:get_chars("tell me>",2).

tell me>er

"er"

2.3.使用io:read/1,它可以从标准输入中读取一个Erlang项元:

io:read("ok,then>").

ok,then>atom.

{ok,atom}

io:read("ok,then>").

ok,then>{2,tue,{mon,"weds"}}.

{ok,{2,tue,{mon,"weds"}}}

io:read("ok,then").

ok,then>2+3.

{error,{1,erl_parse,"bad term"}}

项元是一个求过值的值,而不是任意的Erlang表达式,如2+3.

3.要从标准输出设备中输出,有两种方法:

3.1.由io:write/1所提供,这将打印一个Erlang项元。

3.2.有io:format/2所提供,这将打印一个格式化的项元,io:format可以带有:

一个格式化的字符串或二进制,它们可以控制参数的格式化。

一个需要打印的包含值的列表。

3.3.格式化字符串包含了需要的印刷字符以及控制格式化的序列,控制序列以波形字符(~)开始,最简单的形式是一个单独的字符。

~c:输出一个字符的ASCII码。

~f:输出一个有6个小数位的浮点数。

~e:输出一个以科学计数法表示的总共6位的浮点数。

~w:以标准语法输出任何项元。

~p:输出数据就如~w,但在pretty printing模式下,在适当的地方换行和缩进,在可能的情况下把列表作为字符串输出。

~W、~P:输出就如~w、~p,但限制结构深度为3,在数据列表中它有额外的一个参数来指明打印项元的最大深度。

~B:以10为基数输出一个整数。

完整的控制序列形式为~F.P.PadC。其中F为字段的输出宽度参数,P是它的精度,Pad是填充字符,C是控制字符。例如io:format("the sum of ~W is ~8.2.af.~n",[List,3,Sum]),对其进行解析:~8.2.af:此参数在格式化是一共有8位,其中小数点后有两位,如果不足8位则使用a进行填充,f为前面的控制字符。

递归

1.最好的解决编程问题的办法是久经测试的策略把问题分解成一系列的小问题,把几个简单问题的解决方案联系起来,就可以解决一个意想不到的大问题。

2.终止递归调用的条件称为一种基本情况。

递归-尾递归函数

递归分为两种:

直接递归:这是一种比较直接的调用方法。

尾递归:只要函数f的调用发生在f的函数体的最后一个表达式里,这个函数f就是尾递归。

运行时错误

1.Erlang运行时错误是由系统抛出的异常。

2.错误原因

2.1.function_clause:当已存在的函数模式无一匹配该调用函数的时候就会返回它,此错误通常发生在以下两种情况:要么在条件分析中你忘记了一个条件,要么无意间使用了错误的参数调用函数。例如:

factorial(N) when N > 0->N*factorial(N-1);

factorial(0)->1.

test:factorial(-1).:报错。

2.2.case_clause:当在case结构中没有与现有的模式相匹配时就会返回它,这方面最常见的原因是你忘记了一个或者多个可能的情况。例如:

test1(N)->

case N of

-1->false;

1->true

end.

test:test1(0).:报错。

2.3.if_clause:当在if结构中没有现有的表达式求值是true的时候就会返回它,这是一个简化了的case结构,该错误通常是因为缺少了一个模式。

test2(N)->

if

N<0->false;

N>0->true

end.

test:test2(0).:报错。

2.4.badmatch:错误发生的情况是模式匹配失败,也没有其他可供选择的语句。对于badmatch异常,很难找到单一的原因,但经常性的原因是你无意间尝试绑定已绑定过的变量,例如:

N=45.

45

{N,M}={23,45}.:报错,无法绑定23到N,因为N已经绑定到45了。

另一种比较常见的原因是,你从一个函数调用中匹配获取部分结果。例如在一个元组列表中,使用库函数lists:keysearch/3搜索元组比较常见,成功时返回元组{value,Tuple},其中Tuple就是要搜索的元组。这个函数现在通过下面这个方式调用:

lists:keysearch(1,1,[{1,2},{2,4}]).

{value,{1,2}}

lists:keysearch(3,1,[{1,2},{2,4}]).

false

{value,Tuple}=lists:keysearch(3,1,[{1,2},{2,4}]).:错误,因为我们希望立即使用检索到的元组,但是当没有发现带有匹配关键字的元组的时候,该函数返回false,从而导致出现badmatch。

2.5.badarg:当调用内置函数的时候若使用错误参数会返回它,例如:

length(helloWorld).:length需要一个列表,但是调用时使用了一个基元。

2.6.undef:当没有定义或者导出的全局函数被调用时就会返回它,发生这种异常的原因往往是错误拼写函数名称,或者调用函数时在函数调用前面没有加上模块名称。

2.7.badarith:当算术运算使用不适当的参数就会返回它,如非整数、浮点数或者试图除以0.

处理错误

1.当执行表达式时发生了运行时错误,你可能想捕获异常并且防止终止执行线程。此外,你可能想使其失效,然后让系统的另外部分来处理恢复。

2.处理错误的方法:

2.1.使用try...catch

2.1.1.try...catch结构后面隐藏的思想是对一个表达式求值,并提供处理表达式的正常结果以及异常终止的方法。更重要的是,这个结果允许你区分由Erlang的不同的异常结果处理机制抛出的不同的返回值,以及以不同的方式来处理它们。

在表达式求值之前,你插入关键字try。在case语句中你模式匹配(正常)结果,但不是以end马上终止语句,取而代之的是使用catch语句去处理异常,这些语句包括其头部的一个异常类型(也称为类)和异常模式,还有相应的返回表达式。

try...catch结构具有如下形式:

try Exprs of

Pattern1[when Guard1]->

ExpressionBody1;

Pattern2[when Guard2]->

ExpressionBody2;

catch

[Class1:]ExpressionPattern1

[when ExceptionGuardSeq1]->

ExpressionBody1;

[ClassN:]ExpressionPatternN

[when ExceptionGuardSeqN]->

ExpressionBodyN;

在try...catch中,你可以使用throw/1内置函数来调用一个非本地访问。经过调用栈,try...catch表达式返回传给throw表达式的返回值。

应该避免使用throw,因为作为非本地的返回它会是你的代码很难追踪和调试。

2.1.2.错误类型:

error:这是错误的主要种类,error也可以通过调用内置函数erlang:error(Term)来触发。

throw:这个是由显示调用throw抛出一个异常产生的类,try...catch表达式会捕获它,在Erlang中不推荐使用throw,因为它让理解程序的难度大大增加。

exit:这可以通过调用exit/1内置函数来触发,它包括一个终止的原因,exits也可以通过一个退出信号来产生。

2.1.3.在try...catch中可以使用通配符,如果不是在对返回值模式匹配,则可以省略of。

2.2.使用catch

2.2.1.catch表达式允许你捕获出现的运行时错误。其格式就是"catch表达式",如果表达式计算结果正确就返回表达式的值,但是,如果一个运行时错误发生,它就返回元组{'EXIT',Error},其中Error包含了运行时的错误信息。

2.2.2.在Erlang中优先级有时候是有违直觉的,如果你在catch中绑定一个表达式的返回值,你需要用括号封装catch表达式,给它比赋值更高的优先级,如果不这样做,编译器会返回一个语法错误,例如:

catch 1/0.

{'EXIT',{badarith,[{erlang,'/',[1,0]},

     {erl_eval,do_apply,5},

     {erl_eval,expr,5},

     {shell,exprs,6},

     {shell,eval_exprs,6},

     {shell,eval_loop,3}]}}

X=catch 1/0.

syntax error before:'catch'

X=(catch 1/0).

{'EXIT',{badarith,[{erlang,'/',[1,0]},

     {erl_eval,do_apply,5},

     {erl_eval,expr,5},

     {shell,exprs,6},

     {shell,eval_exprs,6},

                                                  {shell,eval_loop,3}]}}

2.2.3.catch的另一个问题是,它不区分运行时错误的语义,不管是抛出、退出或者是一个函数的返回值,都同样对待它,它没有办法确定{'EXIT',Error}是如何返回得到的,它有可能是在catch中封装的调用执行时发生错误抛出{'EXIT',Error}:或者是一个运行时错误的结果;或者是调用内置函数exit/1的结果,或者仅仅是一个表达式返回了元组{'EXIT',Error}.

模块库

1.文档访问地址

通过访问file://<erl_root_dir>//doc/index.html.

在window下,打开文档的快捷方式包含在Program Files菜单下Erlang/OTP的安装目录里。

在unix系统下,"erl-man"命令是一种方便访问手册的方式。

2.文档左边菜单

文档左边菜单上方有以下链接:

Glossary:

在Erlang中常用的属于清单。

Modules:

Erlang发布版本中包含的按字母顺序排列的模块清单,每个模块都带有网络文档,这包括Erlang和erl模块,许多模块有合适的描述性标题,在浏览器中对这个页面的搜索会让你寻找到所要的功能页面。

Index:

一个对Erlang/OTP函数和命令的排列索引,浏览器搜索在这里再次派上用场,一旦你找到感兴趣的东西,它会提供给你相关模块的链接。

3.有用的模块

3.1.array

array模块包含了一个函数式的可扩展数组的抽象数据类型。它们可以有一个固定的大小或者根据需要将其变大。这个模块包含设置和检查值,以及基于这些来定义递归的函数。

3.2.calendar

calendar模块提供函数来检索本地和全球时间以及提供星期、日期和时间转换。可以计算时间间隔,时间单位可以从日到微秒,calendar模块是基于公历和内置函数now/0的。

3.3.dict

dict模块是一个简单的键值词典,它可以存储、检索和删除元素,合并字典并遍历它们。

3.4.erlang

所有内置函数被认为在erlang模块中实现。关于这个模块的手册页列出了所有的Erlang内置函数,它在不同的虚拟机里是不一样的,因此有些虚拟机自动导入而有些则不是。

3.5.file

file模块提供了一个文件系统的接口,从而能够进行读、操作和删除文件。

3.6.filename

filename模块允许你写通用的文件操作和检查函数,而不用管其在底层操作系统中的文件符号表示。

3.7.io

io库模块封装了标准I/O服务的接口函数,它允许你读取和写入字符串到I/O设备,当然也包括标准设备。

3.8.lists

lists列表操作毫无疑问是在所有主要的Erlang系统中使用最多的库模块,它提供了检查、操作和处理列表的函数。

3.9.math

所有标准的数学函数,包括pi/0,sin/1,cos/1和tan/1都在math库模块中实现。

3.10.queue

queue模块为FIFO队列实现了一个抽象数据类型。

3.11.random

random模块基于提供的一个种子给出了一个伪随机数生成器。

3.12.string

string模块包含了一个字符串处理函数的数组。它和列表模块不同,它会考虑列表模块的内容实际上是ASCII码这个事实。

3.13.timer

timer模块包含了时间相关的函数,它包含了产生时间和转换不同的时间格式为毫秒,毫秒是在这个模块中主要使用的时间单位。

调试器

1.在Erlang中调试器是一个图形化工具,它提供调试有序代码和改变程序执行的机制。它允许用户一步一步运行程序的同时查看和操作变量。你可以设置断点停止执行以及检查递归栈和各个层次的变量绑定。

2.使用debugger:start().来启动调试器,之后会出现一个监视器窗口,这个窗口显示包含追踪编译模块、附加(追踪)进程和其它调试相关设置的一个列表。

3.追踪一个模块,使用debug_info标志来编译它。

unix系统下编译:

erlc +debug_info Module.erl.

window系统下编译:

c(Module,[dubug_info]).

compile:file(exception,[debug_info]).

4.然后通过打开模块菜单中的解释对话框来选择调试器里的模块,跟踪编译的模块和没有跟踪编译的模块将一起在这个窗口中列出,单机你想追踪的模块,然后在它的旁边出现一个*。只要在追踪模块下开始执行一个进程,在监视器窗口上将显示一个条目,你可以双击它,这将打开我们所属的附加窗口,此窗口允许你一步一步执行代码,查看和处理变量,以及检查递归栈。另外打开附加窗口的一个方法是预先在跟踪窗口中附加选项,这些选择包含在代码中设置断点、运行解释模块和退出,或者只在第一次执行时调用解释模块。

5.可以在break菜单中选择一个合适的菜单项或者单机监视器中的行或者模块窗口的行来设置断点,只有在可执行表达式上才可以设置断点,因此设置在头部,模式或者语句分隔符上的断点没有任何效果,断点有一个有效或者无效的状态,当达到一个有效断点的时候,断点可以通过触发器删除、停用或者保持有效。

6.具体执行步骤如下:

6.1.在erlang终端上输入debugger:start()来启动调试器。

6.2.在erlang终端中通过c("模块名",[debug_info]).来将一个模块追踪编译。

6.3.然后打开module中的Interpret,在里面有跟踪的和未跟踪的模块,选择启动一个未跟踪的模块,在调试器窗口中会出现此选择的模块。

6.4.在erlang终端中调用此模块的一个方法,会在调试器中出现一条记录,然后单击双击此记录,会出现附加窗口。

6.5.在附加窗口中的break中选择line Break,conditional break或者function break中的任何一条,然后选择需要加断点的行。

6.6.在erlang终端中调用此模块的一个方法,在附加窗口中可以看到函数执行到的断点处,然后选择附加窗口Process中的Next(跳过此方法调用的内部的方法),continue(执行断点调用的内部方法),finish(完成此模块的调用,即跳过所有的断点)。

猜你喜欢

转载自yansxjl.iteye.com/blog/2357283