《Erlang趣学指南》读书笔记
Contents
第一章
查找帮助
erl -man 模块名
变量名
Erlang 中,变量名不能以小写字符开头。
原子
原子是字面量,这意味着原子是常量,唯一的值就是自己的名字。换句话说,你看到的,就是你能得到的——别想太多。
写法:
- 以小写字母开头
- 如果原子不以小写字母开头或者其中包含有除字母、数字、下划线、以及@符号 之外 的其他字符,那么必须被放到两个单引号 (“) 之间。
注意
千万不要动态生成原子!!!
因为原子表不被垃圾回收,所以原子会一直累积,直到系统因为内存耗尽或者创建的原子数超过了 1048577 而发生崩溃。
有些原子是保留字,这些原子不能使用。
非短路布尔操作符 and 和 or
注意:
这两个操作符,会对两边的参数都会去求值。
短路布尔操作符 andalso 和 orelse
比较
精确
=:= : 精确相等性比较 (严格区分浮点数和整数) =/= : 精确不等性比较 (严格区分浮点数和整数)
非精确
== : 非精确相等性比较(非严格区分浮点数和整数) /= : 非精确不等性比较(非严格区分浮点数和整数)
小于等于
=< :注意,它与其他编程语言是不一样的!!!
不同类型比较
Erlang 语言的发明者把实用性的优先级排在理论前面,觉得如果能简单地写出像排序算法那样的程序,可以对任意数据排序,岂不是很棒!做出这个决定是为了简化编程工作!
number < atom < reference < fun < port < pid < tuple < list < bit string
Erlang 语言发明者之一 Joe Armstrong 说过:具体的顺序不重要,重要是定义明确的全局顺序。
元组
{E1, E2, E3}
带标记的元组
如果一个元组中包含一个原子,原子后面只跟着一个元素,那么这样子的元组就称为带标记的元组。
列表
[E1, E2, E3]
注意
在 Erlang 中,字符串就是列表!!!
++,–
它们都是 右结合 的操作符。即操作是从右向左进行的!!
hd, tl
hd: 获取列表的第一个元素 tl: 获取列表除第一个元素外的所有元素
列表模式匹配
[Head | Tail]
cons 操作符 |
[Term1 | [Term2 | [.. | [TermN]]]]
非良构列表 improper list
[1 | 2]
像这种就是非良构列表。
非良构列表,虽然可以用于模式匹配,但在 Erlang 标准函数(如 length()) 中使用会失败。这是因为 Erlang 期待的是良构列表。
良构列表
这种列表的最后一个元素是 空列表 如:
[2]
[1 | [2]]
列表推导式 list comprehension
用来构建或修改列表。如:
1> [ 2 * N || N <- [1,2,3,4]].
[2,4,6,8]
2>
语法:
NewList = [Expression || Pattern <- List, Condition1, Condition2, ... ConditionN]
Pattern <- List 称为 生成器表达式, generator expression
最完整的语法:
NewList = [Expression || GeneratorExp1, GeneratorExp2, Condition1, Condition2, ... ConditionN]
位语法
<<>>
在这括号里,可以用逗号,将区段之间分隔。一个区段,就是一个二进制的位序列。如:
Color = 16#F09A29
Pixel = <<Color:24>>
匹配模式:
<<Pattern, Rest/binary>>
它与列表模式匹配中的
[Head | Tail]
是一样的。
二进制字符串
<<"Hello word">>
二进制推导式
<< <<X>> || <<X>> <= <<1,2,3,4,5>>, X rem 2 == 0 >>.
与列表推导式类似!
Erlang 适用和不适用的场景
Erlang 历来不擅长数值密集型计算。
对于不需要数值计算的应用,如:事件响应、消息传递等, Erlang 通常是非常快的。
第二章
模块
-module(name).
注意, name 是一个 原子 ,并且要跟文件名相同(除开后缀)
导出函数
-export([Function1/Arity, Function2/Arity,...,FunctionN/Arity]).
导入函数(不太建议这样子使用)
-import(Module, [Function1/Arity, Function2/Arity,...,FunctionN/Arity]).
函数
name(Args) -> Body.
name 必须是一个 原子 , Body 可以是一个或多个用逗号分隔的 Erlang 表达式。函数以一个句点结束。注意,和许多命令式语言不同,Erlang 中没有 return 关键字。return 毫无用处,无需显式说明,函数中最后一个表达式的执行结果会自动被作为返回值传递给调用者。
注释
% 这是注释
宏
-define(Macro, some_value).
使用宏:
?Macro
条件判断:
-ifdef(Macro).
-else.
-endif.
元数据
查看元数据:
moduleName:module_info().
注意
关于模块设计:一定要避免环形依赖。
第三章
模式匹配
Erlang 中,函数中的模式匹配,会同时完成函数选择和变量绑定两件事。无需先绑定变量,然后再去比较它们。
其中,每一条函数声明都被称为一个函数子句(function clause) 。函数子句之间必须用分号(;)分隔,所有函数子句形成一个完整的函数定义。最后一个函数子句以句点结束。
绑定变量和自由变量
变量绑定:就是将一个值附着到一个未绑定的变量上。
在 Erlang 中,如果给一个已绑定的变量赋值,除非这个新值和变量原有的值相同,否则就会引发错误。
卫语句 guard
它是附加在函数头中的语句,能够让模式匹配更具表达力。如:
old_enough(X) when X >= 16 -> true;
old_enough(_) -> false.
在卫表达式中,逗号(,) 的作用和操作符 andalso 类似,分号(;) 和 orelse 类似(不过要注意短路的问题)
不过,卫语句中不能使用用户自定义函数。
if 表达式
也称为 卫模式 guard pattern
if
X > 1 -> io:format("~p > 1~n", [X]);
true -> ok
end.
case … of 表达式
case lists:memeber(X, Set) of
true -> Set;
false -> [X | Set]
end.
第四章
类型
Erlang 是一门强类型的动态语言。
类型转换
erlang:TypeA_to_TypeB().
检测数据类型
is_xxxx().
type_of(X).
第五章
递归
编写递归:
- 基本情形
- 调用自己的函数
- 用于测试的列表
例子:计算列表的长度:
len([]) ->
0;
len([_ | T]) ->
1 + len(T).
尾递归
普通的递归,会进行操作的叠加,而尾递归,则旨在消除这个叠加,方法是在它们出现时,就对其进行归约。
第六章
高阶函数
如果一个函数的参数,是以另一个函数来传递的话,则这个函数称为 高阶函数(higher-order function)
匿名函数和闭包
又称为 funs ,除了不能递归调用自己(因为它是匿名,所以。。。)
注意:不管匿名函数在哪里,被继承的作用域会一直跟随着它!即使把这个匿名函数传递给另外一个函数。
闭包:可以让函数引用到它所携带的带些环境(作用域中的值部分)。
第七章
错误
- 编译期错误
- 逻辑错误
- 运行时错误
异常
- 出错 error : 调用 erlang:error(Reason) . 它会结束当前进程的执行,引发运行时错误。
- 退出 exit : 内部退出: exit/1 ; 外部退出: exit/2 ,在多进程环境中使用。
- 抛出 throw : 抛出异常: throw(permission_denied).
处理异常
try Expression of
SuccessfulPattern1 [Guards] ->
Expression1;
SuccessfulPattern2 [Guards] ->
Expression2
catch
TypeOfError:ExceptionPattern1 ->
Expression3;
TypeOfError:ExceptionPattern2 ->
Expression4
after
Expression5
end.
after 类似其他语言的 finally
第八章
第九章
记录 record
它只是元组的一种语法糖。
定义:
-record(name, {property1, property1....}).
使用:
#recordName{property1=value1, property2=value2...}.
读取
RecordVar = #recordName{property1=v1, property2=v2...}.
RecordVar#recordName.property1.
共享记录: 通过使用 Erlang 的头文件来实现。(xxx.hrl)
在 xxx.hrl 文件中定义:
-record(记录名, {属性1,属性2 ...}).
然后在其他的 erl 文件中包含进来:
-include("xxx.hrl").
K-V 存储
属性列表 proplists:
形如:
[{Key, Value}]
的元组列表。
有序字典 orddict:
{Key, Value}
字典(dict:)和通用平衡树(gb_trees:)(大量数据存储)
字典的读取性能最好,其他的话,可以用 gr_trees
集合
- ordsets : 有序集合(底层实现为有序列表)。主要用于小集合,是最慢、也是最简单的一种。
- sets : 擅长读密集型的处理。
- gb_sets :
- sofs : 集合的集合。
有向图
digraph 模块 和 digraph_utils 模块。
队列
queue 模块
第十章
创建进程
spawn/1
F = fun() -> 2 + 2 end.
spawn(F).
发送消息
Pid ! 'hello world'.
向多个进程同发送同样的消息:
Pid1 ! Pid2 ! Pid3 ! 'hello world'.
接收消息
receive 表达式
receive
Pattern1 [when guard1] -> Exp1;
Pattern2 [when guard2] -> Exp2;
Pattern3 -> Exp3
[after Delay(超时时间,单位是毫秒) ->
AfterExp
end.
第十一章
第十二章
链接 link
当两个进程建立这种关系后,如果其中一个进程由于意外的抛出、出错或退出而死亡时,另外一个进程,也会死亡,把这两个进程独立的生存期绑定成一个关联在一起的生存期。
它们是双向的,并且是不可以叠加的
5> self().
<0.72.0>
6> link(spawn(fun() -> timer:sleep(5000), io:format("link退出~n"), exit(reason) end)).
true
link退出
** exception error: reason
7> self().
<0.76.0>
注意, link 并不是一个原子操作。可以使用:
spawn_link/1-3
来实现它们的原子操作
捕获退出信息
process_flag(trap_exit, true).
监控器 monitor
erlang:monitor(process, Pid).
第一个参数永远是 process ,第二个参数是 Pid
原子版:
spawn_monitor/1-3
命名进程
register(name, Pid).
获取命名进程的Pid:
Pid = whereis(name).
第十三章
第十四章
OTP
init 函数
它负责初始化服务器的状态,并完成服务器需要的所有一次性任务。
它可以返回:
{ok, State}
{ok, State, TimeOut}
{ok, State, hibernate}
{stop, Reason}
ignore
State 会直接传递给进程的主循环,并作为进程的状态一直保存在那里。
handle_call 函数
handle_call/3 用于处理同步消息。它有三个参数: Request, From, State
用于同步消息的处理
它可以有8种返回值:
{reply, Reply, NewState}
{reply, Reply, NewState, TimeOut}
{reply, Reply, NewState, hibernate}
{noreply, NewState}
{noreply, NewState, TimeOut}
{noreply, NewState, hibernate}
{stop, Reason, Reply, NewState}
{stop, Reason, NewState}
handle_cast 函数
它有两个参数:Message 和 State .
用于异步消息的处理。
它可以返回:
{noreply, NewState}
{noreply, NewState, TimeOut}
{noreply, NewState, hibernate}
{stop, Reason, NewState}
handle_info 函数
它和 handle_cast/2 相似,返回值也完全一样。区别在于: 这个回调函数只用来处理直接通过 ! 操作符发送的消息,以及如 init/1 中的 timeout 、监控器通知或 EXIT 信号之类的特殊消息。
terminate 函数
它有两个参数: Reason, State ,分别对应 stop 元组中的同名字段。
注意,所有在 init/1 中的动作都应在 terminate/2 中有对应的取消动作。
code_change 函数
code_change/3 用于代码升级。调用:
code_change(PreviousVersion, State, Extra)
gen_server 实践
在相应的模块中加入:
-behavior(gen_server).
然后直接编译该模块,可以看到它会报:
$erlc demo.erl
demo.erl:11: Warning: undefined callback function handle_call/3 (behaviour 'gen_server')
demo.erl:11: Warning: undefined callback function handle_cast/2 (behaviour 'gen_server')
demo.erl:11: Warning: undefined callback function init/1 (behaviour 'gen_server')
#
第十五章
Erlang 中的有限状态机: 被实现为一个进程,运行一组给定的函数(状态)、接收一些消息(事件)以驱动状态迁移。
即 gen_fsm 行为(finite-state machine, FSM)
init 函数
它和 gen_server 的 init/1 使用方式一样。返回值可以为:
{ok, StateName, Data}
{ok, StateName, Data, Timeout}
{ok, StateName, Data, hibernate}
{stop, Reason}
注意,StateName 它是原子类型。
StateName 函数
函数 StateName/2 (参数分别为 Event ,StateData)和 StateName/3 (参数分别为: Event, From, StateData) 是占位名字,由你来决定它们的内容。假设 init/1 函数返回
{ok, sitting, dog}
这意味着FSM会处于 sitting 状态。
这时,sitting/2 (异步事件)或 sitting/3 (同步事件) 会被调用。
StateName/2 可以返回:
{next_state, NextStateName, NewStateData}
{next_state, NextStateName, NewStateData, Timeout}
{next_state, NextStateName, hibernate}
{stop, Reason, NewStateData}
StateName/3 可以返回:
{reply, Reply, NextStateName, NewStateData}
{reply, Reply, NextStateName, NewStateData, Timeout}
{reply, Reply, NextStateName, NewStateData, hibernate}
{next_state, NextStateName, NewStateData}
{next_state, NextStateName, NewStateData, Timeout}
{next_state, NextStateName, NewStateData, hibernate}
{stop, Reason, Reply, NewStateData}
{stop, Reason, NewStateData}
handle_event 函数
无论当前在哪个状态,全局事件都会触发一个特定反应。(就是这个函数)
参数分别是: Event, StateName, Data
它的返回值和 StateName/2 一样。
handle_syn_event 函数
handle_syn_event/4 和 StateName/3 的关系,类似于 handle_event/2 和 StateName/2 的关系一样。
这个函数处理同步全局事件。参数和返回值都和 StateName/3 一样。
code_change 和 terminate 函数
它们和 gen_server 中完全一样。
第十六章
Erlang 中的 事件处理器,它也是OTP中一种行为: gen_event
gen_event 行为运行这个接受并回调函数的事件管理器进程,而你只需要提供包含这些回调函数的模块即可。
事件处理器的回调函数:
init 和 terminate 函数
它与上面的 gen_server 和 gen_fsm 行为中的类似。
handle_event 函数
handle_event(Event, State) ,它是异步处理的。返回值:
{ok, NewState}
{ok, NewState, hibernate}
remove_handler
{swap_handler, Args1, NewState, NewHandler, Args2}
返回 remove_handler 会导致事件处理器从事件管理器中删除。
最后一个返回值 {swap…} ,这表示移除当前事件处理器,并用一个新的替代它。
handle_call 函数
它与 gen_server 中的 handle_call 回调类似。可返回:
{ok, Reply, NewState}
{ok, Reply, NewState, hibernate}
{remove_handler, Reply}
{swap_handler, Reply, Args1, NewState, Handler2, Args2}
使用 gen_event:call/3-4 可以发起该调用。
handle_info 函数
它只处理带外消息,如退出信号或使用 ! 操作符直接向事件管理器进程发送消息。
与 gen_server 中的 handle_info 使用场景类似。
code_change 函数
参数: OldVsn, State, Extra
第十七章
基本概念:
工作者、监督者、监督树
使用监督者
只要提供一个回调函数: init/1 ,它的返回值为:
{ok, {{RestartStrategy, MaxRestart, MaxTime}, [ChildSpec]}}.
重启策略 RestartStrategy
可以为 one_for_one ,one_for_all ,rest_for_one 和 simple_one_for_one
one_for_one : 当监督者监督了很多工作者,当其中一个工作者失败时,只需要重启这个工作者即可。
one_for_all : 这些工和者必须互相依赖才能正常工作时,就使用这个策略。
rest_for_one : 当需要启动一组进程,而这些进程互相依赖形成一条链时,可以使用该策略。(A -> B, B->C, C-D) 。即,如果一个进程死了,那么所有在这个进程之后启动的进程,都将被重启。反之不然。
simple_one_for_one : 当希望以动态的方式向监督者增加子进程,而不是静态启动子进程时,可以使用这种策略。
MaxRestart 和 MaxTime
意思是在 MaxTime (单位为秒) 指定的时间内,重启次数超过了 MaxRestart 指定的数字,而么监督者会放弃重启,并终止所有子进程,然后自杀,永远停止运行。
ChildSpec 说明
抽象形式:
{ChildId, StartFunc, Restart, Shutdown, Type, Modules}
ChildId : 只是监督者内部使用的一个名称。除了调试或想获取监督者所有子进程列表时,这个名字会比较有用之外,其他基本没什么用了。
StartFunc : 是一个元组,用来指定子进程的启动方式。它采用了 {M, F, A} 的标准格式。
Restart: 指定了监督者在某个特定的子进程死后的处理方式。可以为 permanent (一直要重启), temporary (绝不应该被重启), transient (正常终止就不会重启,异常死亡,比如非 normal 、shutdown、{shutdown, Reason} 就会重启)。
Shutdown: 用来指定终止进程的超时期限 或 是一个原子: brutal_kill ,它表示无条件终止。
Type: 可以让监督者知道子进程是一个监督者(supervisor)还是一个工作者(worker)。
Modules: 一个列表,其中只有一个元素: 子进程行为使用的回调模块名。
第十八章
第十九章
OTP 应用结构
ebin/
include/
priv/
src/
test/
应用资源文件
这个文件要放在 ebin/ 目录下。命名通常是 appName.app
基本结构:
{application, ApplicationName, Properties}.
ApplicationName 是一个原子, Properties 是一个 {Key, Value} 元组列表。
属性子集
{description, “some description of your application”}
{vsn, “1.2.3”}
{modules, ModuleList}
{registered, AtomList} 应用中进程注册名
{env, [Key, Va]}
{maxT, Milliseconds} 应用最长运行时间,默认为 infinity
{applications, AtomList} 依赖其他应用的列表。在自己的应用被加载/启动之前,Erlang的应用管理系统会确保这些被依赖的应用先被加载或启动。
{mod, {CallbackMod, Args}} 启动应用时,会调用 CallbackMod:start(normal, Args) ,停止时,会调用 CallbackMod:stop(Args)
启动应用
application:start(AppName, temporary) => 正常结束,则不处理,只有这个应用停止运行。如果异常结束,则报告出错消息,这个应用被终止,不再重启。
application:start(AppName, transient) => 正常结束,则不处理,只有这个应用停止运行。如果异常结束,则报告出错消息,其他所有应用会被停止,VM也会被关闭。
application:start(AppName, permanent) => 正常结束或异常结束,其他所有应用都会被停止,VM也会被关闭。