erlang入门(一)

来源:互联网 发布:手机淘宝6.0.0旧版本 编辑:程序博客网 时间:2024/04/28 09:48

Erlang程序写在文件中。每个文件都包含一个Erlang模块(Module)。


文件名tut.erl这一点很重要,同时需要确定文件与erl在同一个目录下.


{ok,tut}告诉你编译成功。如果它提示"error",你可能在输入文本的时候出错,并而错

误信息可能会给你一些有关于如何纠正错误的想法,依此你可以改变你的代码,重新再试。


现在让我们运行这个程序。

4> tut:double(10).


Erlang程序写在文件中。每个文件都包含一个

Erlang模块(Module)。在文件中的第一行,就告诉我们模块的名称(See the

chapter "Modules" in the Erlang Reference Manual)。


-module(tut).

这告诉我们模块的名称是tut。注意本行结尾的"."。存放模块代码的文件的名字,也必须

和模块同名但以".erl"做为扩展名。。当我们使用另

一个模块的函数,我们使用语法,模块名:函数名(参数)。所以

4> tut:double(10).

意味着我们调用tut模块中的double函数,并使用"10"做为参数。


-export([double/1]).

说明tut模块包含一个名称为double的函数,并且带有一个参数(在我们的例子中为X)

并而这个函数可以在tut模块以外被调用。


一个数字的阶乘(如:4的阶乘是4*3*2*1)。在名为

tut1.erl中输入下面的代码。

-module(tut1).

-export([fac/1]).

fac(1) ->

1;

fac(N) ->

N * fac(N - 1).


第一部分:

fac(1) ->

1;

说明1的阶乘是1。注意我们以一个";"结束这一部分,这说明这个函数没有结束。第二部

分:

fac(N) ->

N * fac(N - 1).

说明N的阶乘是,N与N - 1的阶乘的乘积(N! = N * (N - 1)!)。注意这部分

以"."结束,以说明本函数没有其它部分了。


-export([fac/1, mult/2]).

mult(X, Y) ->

X * Y.

注意,我们同时也扩展了-export行,并给于它关于另一个带有两个参数的函数信息。


变量首字母必须是大写.


1.2.3 元子(Atoms)

元子是在Erlang中的另一个数据类型。元子以小写字母开头.

元子只是一个简单的名字,其它什么都不是。他们不像变量可以带有一个值。


1.2.4 元组

元组以"{"和"}"括起来的。不过元组可以有很多部分,我们想要多就都可以。

例如:

{inch, X} {moscow, {c, -10}}

一组元组有一个固定的大小。我们称元组中的东西为‘元素’。所以在元组{moscow,{c,-

10}}中,元素1是moscow,元素2是{c, -10}。


1.2.5 列表

列表在Erlang中被括在"["和"]"里。

Erlang可允许分成多行,不过,不可以在元子或整数中间的某部分来分。

一个很有用的遍历列表的方法是使用"|"。

[First |TheRest]= [1,2,3,4,5]

我们使用|来分隔列表中的第一个元素和后续的元素。


[E1, E2 | R] = [1,2,3,4,5,6,7]

这里的|是用来得到前两个元素。当然,如果我们试着从列表中得到比列表中定义的元素个数更多的元素的话,我们会得到一个错误。

我们通常所说的使用元组,在其它的编程语言中,我们可能会叫做“记录”或“结构体”。我们使用列表(我们在其它编程语言中所什么的“链表”)。


Erlang并没有字符串类型,取而代之我们可以提供一个由ASCII字符组成的列表。


1.2.6 标准模块及用户手册

io:format/2函数自身返回一个元子ok,注释以%开始,直到本行结束。同时也要注意-export([format_temps/1]).一行只包含format_temps/1函数,另一个函数是局部(local)函数,他们无法被tut5模块以外的东西访问。


1.2.9 变量的匹配、守卫和作用域

首先注意我们这里有两个同名的函数list_max。虽然每个都带有不同数据的参数。在Erlang这些都会被认为是完全不同的函数。

我们需要通过“名称/参数数量”区分这些我们写的函数,比如在这里是list_max/1和list_max/2。


在->之前的特定字符,表示--我们只会在这个特定的条件满足的时候,才会执行函数的这个部分。我们叫这种类型的测试,叫守卫(guard)。如果守卫不是‘真’ (我们称它为守卫失败),我们会尝试执行函数的下一个部分。这种情况下如果Head不大于Result_so_far的话,它必定是小于等于它,所以我们在下一部分中,不需要再使用守卫。一些守卫中常见的操作符有< 小于,> 大于,== 等于,>= 大于等于,=< 小于等于,/= 不等于。


Result_so_far给赋了很多值。那是因为,我们每次调用list_max/2的时候,我们建立了一个新的作用域,Result_so_far在这个作用域中,都会被认为是完全不同的变量。

另一个建立和给一个变量赋值的方法是使用 =。如果我写M = 5,那么一个名为M的变量会被创建,并赋于5这个值。如果在同一下作用域下,当我写M = 6,它就会发生错误。


匹配操作符在通过一组元素创建新变量的时候很有用。

{X, Y} = {paris, {f, 28}}

这里我们看到X的值被赋于paris,而Y是{f,28}。


1.2.10 更多关于列表

|也可以用来在列表的头部添加元素:

模块lists包含了很多函数用于维护列表,例如反转列表。所以在我们写一个列表函数之前,最好先看一下是否在库中已经存在了这样一个函数。



1.2.11 If和Case

if

Condition 1 ->

Action 1;

Condition 2 ->

Action 2;

Condition 3 ->

Action 3;

Condition 4 ->

Action 4

end

注意,在结尾没有";"!条件(Conditions)和守卫一样,测试成功或是失败。Erlang会从最顶上开始向下,直到找到一个成功的条件为止,并执行行条件下面的动作,并且乎略掉其后面它的条件。如果没有条件匹配,会发生一个运行时(run-time)错误。元子true在条件中表示真,它常常被放在条件层,表示如果没有匹配的条件的话,应该做什么动作。


convert_length(Length) ->

case Length of

{centimeter, X} ->

{inch, X / 2.54};

{inch, Y} ->

{centimeter, Y * 2.54}

end.

注意,case和if都有返回值,即,在上面的例子中case返回{inch,X/2.54}或 {centimeter,Y*2.54}。case的行为也可以使用守卫来代替。


1.2.12 内建函数(BIFs)

内建函数(BIFs)是一个因为某原因被内嵌在Erlang虚拟机中的函数。BIFs经常是一些在Erlang上无法实现的功能,或在Erlang下实现起来效率不高的东西。有些BIFs可以直接使用函数的名字,他们默认是属于erlang模块下的函数。比如trunc函数,和 erlang:trunc调用是等同的。


只有很少一部分的内建函数,可以于用守卫,你也无法在守卫中使用你自己定义的函数。

(see the chapter "Guard Sequences"(http://www.erlang.org/doc/reference_manual/expressions.html) in the Erlang Reference Manual)。


1.2.13 复杂函数


convert_to_c函数,和以前定义的一样,不过我们把它做为一个带入函数来用: lists:map(fun convert_to_c/1, List)


当我们使用一个要别处定义的fun函数的话,我们可以能过函数名/参数个数,来区别。


在sort函数中,我们使用的fun函数: fun({_, {c, Temp1}}, {_, {c, Temp2}}) -> Temp1 < Temp2 end,

这里,我们介绍匿名变量”_”。这是一个简单化的,用于一个将得到值的变量,并且我们还需要丢弃这个值的情况下。它可以用在任何合适的场合中,不只在fun中使用。Temp1 < Temp2返回true如果Temp1小于Temp2。


1.3 并行编程

1.3.1 进程

并发,我们的意思是一种可以处理掌握若干线程同时执行这样的程序。

”进程”经常指线程间没有共享数据,”线程”指他们之间,有共享的数据或共享内存区

Erlang内建函数spawn被用于建立一个新的进程:spawn(模块, 导出的函数, 参数列表)。

spawn返回一个进程ID,或pid,即一个进程的唯一标识。所以<0.63.0>是spawn函数的pid,即进程ID。


1.3.2 信息传递


receive构造用于允许进程等待来自其它进程的消息。它的格式是:

receive

pattern1 ->

actions1;

pattern2 ->

actions2;

....

patternN

actionsN

end.

注意,在结尾没有”;”。

在Erlang进程间的消息是一个有效的Erlang字串,即,它们可以是列表、元组、整数、元子、pid等等。


每个进程对于它收到的消息,有一个自己的输入队列。新的消息会放在队列的尾部。当一个进程执行了一个receive,队列中第一个与匹配条件一致的消息就会从队列中移除,并匹配的动作会执行。


如果第一个匹配试没有被匹配的话,第二个就会被检验,如果与队列中移除的条件匹配了的话,相对于第二个式子中的运作会被执行。第三个也依次类推。如果没有匹配的话,那么第一个消息就会被放回队列中,并用第二个消息来做对应的操作。如果第二个消息匹配的话,相应的动作就会被执行,并把第二个消息从队列中移除(并保留第一个消息和其它的消息在队列中)。如果第二个消息也不匹配,我们会试着用第三个消息,直到达到队列尾。如果到达到队列尾部,进程就会阻塞(中止执行)并等待,直到一个新的消息被收到,上面的过程重复。


注意,”!”是如何发送消息的,以及”!”的句法:

Pid ! Message

即,Message(任何东西)被发到给了标识为Pid的进程。


Pong_PID ! {ping, self()},

self()返回当前自在运行的进程ID,在这里是”ping”的进程ID。


1.3.3 进程名称注册

有些时候进程可能需要知道每一个与它不相关的,启动的进程的标识。

这是由register这个内建函数完成的:

register(some_atom, Pid)


1.3.4 分布式编程

分布式Erlang的实现,提供了一个基本的安全机制以防止来自其它计算机的非授权的访问(*manual*)。


Erlang系统要想互相通信,必需要相同的magic cookie。最简单的获取它的方法是在你的每台需要Erlang通信的机器中的home文件夹中建立一个.erlang.cookie的文件(在Windows系统中, home文件夹由$HOME环境变量指定- 你可能需要首先设定它。在Linux或Unix系统中,你可以忽略这一步,只需要简单的在你用户home文件夹中,建立.erlang.cookie 就可以了)。.erlang.cookie文件中需要包含相同的元子字串。如Linux或Unix系统

中:

$ cd

$ cat > .erlang.cookie

this_is_very_secret

$ chmod 400 .erlang.cookie

上面的chmod使.erlang.cookie文件只可以被它的所有者访问。这是必需的。


当你启动一个需要与其它机器上的Erlang系统交互的一个Erlang系统时,你必须给它一个名字,如:

erl -sname my_name

如果你希望体验一下分布式Erlang,可是你只有一机计算机的话,你只需要启动两个独立的Erlang系统在同一台机器上,并给他们不同的名字就可以了。每一个运行于计算机上的Erlang系统,被称为一个Erlang节点。


(注意:erl -sname 假设所有节点都在同一个IP域下,如果我们想要使用其它IP域上

的节点,我们使用-name代替,并而需要给出完全的IP地址


Erlang的pid已经包含它了有关于进程运行的相关信息,所以你只要知道pid,”!”就可以用于发送消息,无论目的地是在同一个节点上,还是其它节点上。


不同的是,我们如何把消息发送到另一个节点上注册的进程中: {pong, Pong_Node} ! {ping, self()}, 我们使用元组{regiester_name, node_name}代替 注册名。


一个Erlang进程理论上将一直运行,直到收到不需要的信息。之所以我说“理论上”因为 Erlang系统与活动进程运享CPU时间。


当没有事可做的时候,一个进程会终止,即,最后一个函数被调用,并返回一个值,而且不再调用其它函数了。另一个中止进程的方法是使用exit/1。exit/1的参数有特定的含意,我们后面再说。


内建函数whereis(RegisteredName)检测如果一个注册的进程名称叫 RegisteredName存在,如果存在,返回pid。否则返回undefined。


1.4健壮性(Robustness 鲁棒性)

  1. 超时(Timeouts)

    超时设这段代码中:

pong() ->

receive

{ping, Ping_PID} ->

io:format("Pong received ping~n", []),

Ping_PID ! pong,

pong()

after 5000 ->

io:format("Pong timed out~n", [])

end.

进入receive代码段时,超时(after 5000) 如果接了消{ping,Ping_PID}超时将,如果该消息,在5000毫秒之后,超时代码段作将行。after语句必须receive代码段的最后一个匹配件。 receive代码段中的其匹配义优处理。 当然,我们也可以使用一个一个数的函数超时的设定值after pong_timeout()



  1. 错误处理

一个进程如果使用exit(normal)退行完所有的代码然退出称(normal)退.

一个进程如果发生了运行时错误(例如 0,错误的匹配,试图调用一个不在的函数.错误而结束也就(abnormal)退出。

进程通过调link(Other_Pid) (*manual*)立自Other_Pid进程的双向接。 一个进程中的时,将给每一个它建立了接的进程发一个信(signal)个信号包含了者的pid和进程结束的原

你可以将一个(transaction)及到的所有进程接在一,如果其中的一个进程常退出,所有的该事中的进程将全部

函数spawn_link在完成spawn函数能的时建立了一个建的进程的


可以一个进程接常退出信时的缺省退出行时所有的信号都被转换成一个{'EXIT',FromPID,Reason}通消息并且被列中。我们通过下语句来实该功能:

process_flag(trap_exit, true)

还有很多的进程标(flag),变进程缺的方式在标准的用户程序中并不,更多的用在OTP的监控(supervisory)程序中


  1. [#1]记录和宏(Records and Macros)

1.5.2 头文件(Header Files)

你可以些扩展.hrl

原创粉丝点击