《Erlang/OTP并发编程实战》读书笔记- 第二章

第二章 Erlang语言精要

2.1 Erlang shell

Erlang shell 被设计用于持续运行,是为交互式开发、调试和升级而设计的。

2.1.1 启动shell

erl
shell 提示符

2.1.2 输入表达式

        在shell提示符下输入的不是命令,而是表达式。表达式一定会返回一个求值结果, 并在shell打印出来。用句号告诉shell表达式输入完毕。可以用函数v()来取出求值结果。默认情况下shell会保存最近20条求值结果。

2.1.3 shell函数

2.1.4 退出shell

1.调用q()或init:stop()

2.BREAK菜单

 

2.2 Erlang的数据结果

2.2.1 数值与算术运算

Erlang有两种数值类型:整数,浮点数
Erlang整数没有大小限制。浮点数采用64位IEEE 754-1985格式,且必须以数字开头。

        Erlang中常用的撒孙淑运算符都采用常见的中缀表示法,+、-、*符合常规。如果参与二元2运算的两个参数中有浮点数,运算将被转成浮点型。除法分为两种,/运算符,它总是返回浮点数;div为整除。

2.2.2 二进制串与位串

        二进制串就是无符号8位字节的序列。位串是广义的二进制串,长度不必是8的整数。

2.2.3 原子

        原子是一种仅由字符序列来标识的特殊字符串常量, 这些字符串存放在某张表内,并由表的下标定位,常用作标签。通常原子以小写字符开头,首字母后可以用大写字母、数字、下划线和@,如果还有用到其他字符需要加上单引号。

原子的长度上限是255个字符,在单个系统中原子的总数也有一个上限,目前是一百多万(1048576)。原子一经创建,即便不再使用也永远不会被清除,除非系统重启。

常用原子:true、false、ok、undefined

2.2.4 元组

        元组用大括号来表示,可以包含零个、一个或多个元素。这些元素可以是同类型也可以是不同类型。

2.2.5 列表

        用方括号表示列表,[]为空表.
添加列表元素:|(管道符), [1| List]; 或 [1] ++ List.

2.2.6 字符串

        Erlang中的双引号字符串实际上就是列表,其元素就是该字符串中各字符的数值编码所对应的整数.

2.2.7 pid、端口和引用

pid(进程标识符),每个进程都有一个唯一的标识,称作pid. shell打印的格式为<0.35.0>
端口标识符,shell打印的格式为#Port<0.472>
引用:常被用作保证唯一性的一次性标签或cookie,可由函数make_ref()生成,shell打印格式为#Ref<0.0.0.39>.

2.2.8 fun函数

函数可以成为别的函数的输入,也可以成为别的函数的求值结果。

2.2.9 项式的比较

Erlang的各种数据类型都可以通过>、< 和==运算符进行比较,不同类型之间也可以进行比较.比较的规则是:数值 < 原子 < 元组 < 列表

小于等于 =<, 大于等于 >=

完全相等 =:=,(值和类型都相同) ==算术相等

2.2.10 解读列表

        列表一般由空表和列表单元共同构成.这些单元格子携带一个元素挨个挂接到现有列表的顶部,从而在内存中形成一个单链表..每个单元仅占用两个字节的内存空间,一个用于存放元素值(或指向元素值的指针)称为首部,另一个是指向列表其余部分的指针 ,称为尾部。 

 

2.3 模块和函数

2.3.1 调用其他模块中的函数(远程调用)

        要调用其他模块中的函数,需要在函数名之前以冒号为分隔符加上函数所处模块的名字。这种形式的函数调用被称为远程调用。与之相对的是本地调用(调用同一模块中的函数)。

2.3.2 不同元数的函数

        函数参数的个数称作元数。Erlang没有函数重载机制,两个函数使用相同的原子  作为函数名,只要元数不同,它们就是不同的函数。

2.3.5 模块的编译和加载

        编译模块时,会产生一个和模块对应的扩展名为.beam而非.erl的文件,其中包含可能 被Erlang系统加载执行的指令.

2.4 变量与模式匹配

2.4.1 变量的语法

变量名必须以大写字母开头,变量名中的单词以驼峰体隔开,是erlang变量的标准命名风格。
变量名也可以以下划线开头(一般用于给不会使用到的变量命名)

2.4.2 单次赋值

Erlang的变量只接受单次赋值,也就是变量的值是不可变的。

=运算符,是一个匹配运算符,

2.4.3 模式匹配

运算符的左侧 是一个模式,右侧是一个普通表达式。做匹配运算时,首先计算右边的表达式,得到一个值。接着拿该值去匹配左侧的模式。

用++完成字符串前缀匹配你

"http://" ++ Rest = "http://www.erlang.org"

2.8 异常与try/catch

Erlang的异常分为三类:
error——运行时异常,在发生除零错误、匹配运算失败、找不到匹配的函数等情况时触发。一旦它们使某个进程崩溃,Erlang错误日志管理器便会记录在案。
exit——这类异常用于通报“进程即将停止”。在迫使进程崩溃的同时将进程退出的原因告知其他进程。
throw——用于处理用户自定义的情况。

2.8.2 运用try...catch

 如果执行过程中抛出了异常错误,那么就会在catch和end之间的子句里进行匹配

2.8.4 after

        这种机制常用于完成各种形式的资源释放。

2.8.5 获取栈轨迹

        可以通过调用内置函数erlang:get_stacktrace()来查看当前进程最近抛出的异常的栈轨迹。
栈轨迹是异常发生那一刻位于栈的顶部的那些调用的逆序列表(最后一个调用的位于最前)

2.8.6 重抛异常

        可以先捕获异常,再通过内置函数erlang:raise(Class, Reason, Stacktrace)重新将之抛出。此处的Class必须是error、exit或throw;Stacktrace应该来自erlang:get_stacktrace().

2.9    列表速构

2.9.1 列表速构记法

双竖线||右侧为生成器和约束条件,左箭头<-表示生成器;其他部分为约束条件.
双竖线||左边是模板部分,可以是任意表达式.

2.9.3 映射、过滤和模式匹配

        列表速构可以用于完成各种映射和过滤的运算组合,其中映射是指针对元素完成一些运算后再将运算结果放入结果列表。
        在生成器中,<-箭头左侧不一定是变量--可以是任意模式,跟匹配运算符(=)差不多。

2.10 比特位语法与位串速构

2.10.3 位串速构

位串速构类似于列表速构,只是把列表[...]换成<<...>>。例如:把所有整数按每个数3比特位打包成位串:

得到的位串打印为<<41,203,23:5>>,把<-生成器换成<=,表示从位串中提取内容,而<-只能从列表中选取元素;

 

2.11 记录语法

2.11.1 记录声明

 2.11.2 创建记录

可以用以下语法来创建新的记录元组:

记录名前必须加上#,编译器才会将之与记录声明相匹配。在{...}之内,可以选择任意字段按任意顺序进行赋值(一个不选也可以)。未赋值的字段将被置为默认值。

2.11.3 记录的字段以及模式匹配

 
和元组做匹配类似,但不需要关心字段的数量和顺序。

2.12 预处理与文件包含

Erlang的预处理与C/C++类似,是语元级预处理器。预处理器负责3个重要任务:宏展开、文件包含和条件编译。

2.12.1 宏的定义和使用

    宏由define指令定义。命名上变量和原子的命名规则都使用,一般常量名为大写,其余为小写。
在代码中使用宏时,必须加上一个问号作为前缀。

取消宏定义:undef指令可用于移除宏定义(前提是该宏定义存在)。

2.12.2 文件包含

通过使用包含指令,Erlang源码文件可以包含另一个文件,形式如:
-include("filename.hrl").
Erlang的头文件以.hrl为扩展名。

2.12.3 条件编译

条件编译就是让编译器按特定条件忽略程序的某些部分。这种手法常用于生成程序的多种版本,比如专用于调试的版本。一下预处理指令可以控制编译器再特定的实际忽略特定位置的代码:

-ifdef(MacroName).
-else.
-endif.

    ifdef和ifndef用于测试某个宏是否被定义过,每个ifdef或ifndef,都必须有endif来标识条件编译区的结束。else可以将条件编译区分为两段。

2.13 进程

2.13.1 操纵进程

1.派生和链接

派生函数:

Pid = spawn(fun() -> do_something() end).
Pid = spawn(Module, Function, ListOfArgs).

链接: 

link(Pid).

先派生进程再用link()创建链接会引入竞态条件,spawn_link()可以确保进程创建于进程链接创建的原子性,避免竞态条件.

2.进程监视
        链接有一个替代品,称作监视,是一种单向链接,可以让一个进程再不影响目标进程的情况下对苗木表进程实行监视.

Ref = monitor(process, Pid).

3.靠抛异常来终结进程
        exit类异常用于终止运行中的进程.这类异常可通过BIF exit/1抛出,除非被捕获,否则该调用将令进程终止,并将Reason作为退出信号的一部分发送给所有与该进程链接的所有进程 。

4.直接向进程发送退出信号
        进程除了在意外退出时会自动发送信号外,还可以直接向其他进程发送退出信号。收发双发无须链接:exit(Pid, Reason).
        exit/2和exit/1是完全不同的函数,该信号终止的不是发送方而是接收方。

5.设置trap_exit标志
        默认情况下,一旦接收到来自相互链接的其他进程的退出信号,进程就会退出.为了避免这种行为并捕捉退出与信号,进程可以设置trap_exit标志:process_flag(trap_extit, true).
这样一来处理无法捕获的信号(kill)以外,外来的退出信号都会被转换成无害的消息.

2.13.2 消息接收与选择性接收

        接收消息的进程可以用receive表达式从信箱中提取消息。接收到的消息严格按照抵达顺序排列,但可以选择性地忽略当前暂不相关的消息(比如提前到达的消息)。

 如果某个子句的模式与当前消息匹配成功,该消息便会移出信箱,如果在进入接收表达式的Time毫秒后还没有收到匹配的消息,进程就会停止等待消息,转而执行TimeoutBody,信箱保持不变。

2.13.3 注册进程

管理注册进程的内置函数有:
registered() :返回包含系统里所有注册进程的列表。
whereis(AnAtom) : 查找当前与指定注册名对应的pid。
register(AnAtom,Pid) : 注册进程Pid。

2.13.5 进程字典

        每个进程都有一个私有的进程字典,这是一个可以用任何值做为键的简单哈希表。通过内置函数put(Key, Value)和get(Key)可以从中存取.

2.14 ETS表

2.15 以递归代替循环

除列表速构以外,这门语言别无其他迭代结构。原因在于Erlang靠递归函数调用代替了迭代

递归调用可以分为两类:尾递归和非尾递归。

        尾调用优化的意思是,当编译器识别出尾调用(函数返回前的最后一个任务)时,会生成一段特殊的代码,这段代码会在执行尾调用之前从栈中扔掉当前调用的所有信息。此时当前调用基本无事可做,只需告知被调用的函数后续即将发生一次尾调用:“嘿!完事儿的时候直接把结果告诉我的调用者就行了,我收工了哦。”因此,尾调用不会导致栈的膨胀。(作为特例,调用相同函数的尾递归调用可以重用栈顶的调用信息,省得扔掉之后还要再重新创建。)本质上,尾调用只是“在必要时清理一些东西,然后执行跳转”。
        正是基于这个原因,尾递归函数即便不停不歇地运行也不会将栈空间耗尽,同时还能达到和while循环一样高的效率。

效率:

  一般来说尾递归方案比对应的非尾递归方案更为高效 ,但不绝对,这取决于具体的算法。如果需要追踪的信息很复杂,需要完成一些复杂的数据管理工作,尾递归反而会慢上一拍。

在尾递归和非尾递归实现中选择更可读,更易维护,更有把握正确实现的方式。

用到无循环的函数就只能采用尾递归。我们称这类函数在运行时空间消耗恒定,即便函数永不返回,其内存占用量也不会随时间的流逝而增加。

编写递归函数的窍门:

小心长度:在Java这类语言中,获取列表长度是一个常数时间复杂度。
在Erlang中,length(List)会遍历整个列表,所以不要在递归函数中调用length()函数。用模式匹配对列表判空,检查不同长度的列表。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值