erlang入门笔记
2008-06-20
版权声明
:转载时请以超链接形式标明文章原始出处和作者信息及
本声明
http://cbkid.blogbus.com/logs/23258709.html
1.1.2 其它方面
文章省略了以下几个方面:
● 参考
● 本地错误处理 (cache/throw)
● 单向连接 ( 显示器 )
● 二进制数据处理
● 列表相关
● 与外界如何通信,以及 / 或者 port 其它语言开发的软件。当然,有一些向导中会单
独讲解这个问题。 < 互操作向导 >
● 涉及到的极少数 Erlang 库 ( 如,文件处理 )
● 关于 OTP 的问题完全被跳过,关于 Mnesia 数据库的信息在结论中也被省略。
● Erlang 中的哈杀表。
● 运行时改变代码。
1.2.2 模块和函数
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 鲁棒性 )
-
超时 (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() →
-
错误处理
一个进程如果使用 exit(normal) 退 出 或 者 运 行完所有的 代码然 后 退 出称 为 正 常 (normal) 退 出 .
一个进程如果发 生了运 行时错误 ( 例如 除 以 0 ,错误的匹配, 试图调 用一个不 存 在的函数 等 . 将 因 为 错误而 结束 , 也就 是 异 常 (abnormal) 退 出。
进程 通过调 用 link(Other_Pid) ( * manual * ) 来 建 立自 身 和 Other_Pid 进程的 双向 链 接。 当 一个进程中 止 的时 候 ,将 给每 一个 与 它建 立了 接的进程发 送 一个信 号 (signal) 。 这 个信 号包含了 发 送 者的 pid 和进程 结束 的原 因 。
你可以将一个 事 务 (transaction) 所 涉 及到的所有进程 连 接在一 起 , 如果其中的一个进程 异 常退 出,所有的 该事 务 中的进程将 被 全部 杀 死 。
内 嵌 函数 spawn_link 在完成 spawn 函数 功 能的 同 时建 立了 一个 与 新 创 建的进程的 链 接
我 们 可以 修 改 一个进程接 收 到 异 常退 出信 号 时的缺 省退 出行 为 , 这 时所有的信 号都 将 被转换 成一个 格
发表评论
评论