笔记系列
Erlang环境和顺序编程
Erlang并发编程
Erlang分布式编程
Yaws
Erlang/OTP
日期 变更说明
2014-10-12 A outline, 1, 3
2014-10-19 A 2.1, 2.2
2014-10-26 M 2.1, 2.2
A 2.3
2014-10-27 A 2.4 类型系统
前言
拖了很久很久了,终于快忘的差不多了。
Agenda
0 范围
环境
Erlang运行时环境、Emacs开发环境
顺序编程
数据结构
语法和语义
1 Erlang环境搭建
Erlang website
实践版本
otp_win32_R16B/erl5.10.1
安装路径记为ERLANG_HOME=D:/erl5.10.1
获取环境变量的方法见杂项一节。
Javaer Eclipse
Erlang plugin (ErlIDE) for Eclipse
Emacs开发环境
Emacs版本: GNU emacs-24.3
完整的Emacs的Erlang模式设置见:
http://www.erlang.org/doc/apps/tools/erlang_mode_chapter.html
A sample:
;; erlang (setq load-path (cons "D:/erl5.10.1/lib/tools-2.6.10/emacs" load-path)) (setq erlang-root-dir "D:/erl5.10.1/lib/tools-2.6.10") (setq exec-path (cons "D:/erl5.10.1/lib/tools-2.6.10/bin" exec-path)) (require 'erlang-start)
自动补全hint
http://*.com/questions/3762583/emacs-textmate-code-completion-for-erlang
http://www.erlang.org/doc/man/erlang.el.html
离线文档
文档路径记为DOC_ROOT=D:/erl5.10.1/doc
应用索引页: DOC_ROOT/applications.html
模块索引页(OTP Reference Page Index): DOC_ROOT/doc/man_index.html
Erlang Quick Reference Card:https://github.com/stolowski/Erlang-Quick-Reference-Card/blob/master/erlang-quickref.pdf
2 Erlang顺序编程
2.1 数据结构
变量
Erlang的变量词法上以大写字母开头。
Erlang变量不会变,或者称为一次性赋值变量。
赋予了值的变量称为绑定变量,否则称为未绑定变量。
LHS = RHS.
=是一次模式匹配操作,先计算RHS的值,将结果与LHS的模式项匹配。
与C系列命令式语言中可变变量所带来的错综复杂的副作用相比,一次性变量极大的方便了错误定位。
作用域
变量的作用域是它定义时所处的语汇单元。
没有一个函数的不同子句共享全局或私有变量这种说法。
“全局变量”
每个Erlang进程中有一个称为进程字典的私有数据存储区域,进程字典是一个关联数组,等价于map、hashmap或散列表。
引用(reference)是全局唯一的Erlang数据类型,用于创建唯一的标签;用BIF erlang:make_ref()创建。
Erlang的原子。
匿名变量、下划线变量
匿名变量词法用下划线_表示,告知编译器这是一个无关紧要的变量,不用于绑定。同一模式中多个_不必绑定相同的值。
下划线变量_VarName,是常规变量,用于屏蔽在子句中仅使用了一次的变量所产生的编译器警告。
数值
整数
Erlang可以用任意长度的整数执行整数运算,只受限于可用的内存。
表示法:
(1)传统写法
(2)K进制整数:K#Digits,最高的进制数是36;
(3)$写法:$C表示ASCII字符C的整数代码。
浮点数
词法:[+|-] 整数部分 . 分数部分 [指数部分]
指数部分::= (e|E) (+|-) 整数
Erlang内部使用IEEE 754的64位格式表示浮点数。
原子
Erlang的原子表示常量值,词法上以小写字母开头,后接字母、数字、_或@,或者放在单引号(‘)内,单引号内可以放置任意符号。
双引号(“)与单引号(‘)不可互用。
Erlang原子是全局性的,不需要宏定义或包含文件就能实现。
元组
Erlang的元组(tuple)词法:{term1, term2, …}
带标签的元组:{representation_label, term1, term2, …}
列表
Erlang的列表(list)词法:[term1, term2, …]
如果T是一个列表,则[H | T]也是列表,它的头部是H,尾部是T。[]是空列表。
格式正确的列表:用[… | T]创建的列表,且T是列表。
列表推导(list comprehension)
概念:根据现有列表生成新的列表的语法糖
语法:[X || Qualifier1, Qualifier2, …]
其中X可以是任意表达式,限定符Qualifier可以是生成器(generator)、位串生成器(bitstring generator)、过滤器(filter)
Generator:Pattern <- ListExpr, ListExpr是结果为列表的表达式
Bitstring Generator:BitStringPatter <= BitStringExpr, BitStringExpr是结果为位串的表达式
Filter:判断函数(返回true/false)、布尔表达式
► A sample
main() -> L = [1,2,3,4,5], io:format("~p~n", [[2*X || X <- L]]), io:format("~p~n", [[X || {a, X} <- [{a,1}, {b,2}, {c,3}, {a,4}, hello, "world"]]]).
自然顺序的列表
概念:总向列表头添加元素;从输入列表头部提取元素,放入输出列表的头部;如果元素顺序很重要,使用lists:reverse/1这个高度优化过的函数。
字符串
Erlang字符串是一个整数列表或一个二进制型,其中整数列表中元素表示一个Unicode码点。
字符串字面量:用””包含的字符序列。
布尔值和布尔表达式
Erlang没有单独的布尔类型,原子true、false可以用来表示布尔值。
布尔表达式有四种:
not B
B1 and B2
B1 or B2
B1 xor B2
其中,B、B1、B2必须是布尔值或结果为布尔值的表达式。
短路布尔表达式(short-circuit Boolean expression)
Expr1 orelse Expr2
Expr1 andalso Expr2
此概念与命令式语言(C/Java)中or 、and语义一致,相对的Erlang布尔表达式操作符 and/or会严格计算两边之后再计算表达式结果。
记录和映射组
记录
记录声明命名元组中的元素,语法:-record(Name, {Key1=Default1, …, Key3,…}).
惯用法:将记录定义在.hrl文件中,可以在多个包含.hrl文件的模块*享该记录定义。
记录在内部实现意义上是元组的另一种形式。
►record_sample.hrl(记录也可以定义在模块文件中)
-record(todo, {status=reminder, who=zhoujiagen, text}).
记录创建和更新
-include("record_sample.hrl"). create_update() -> Todo1 = #todo{}, io:format("~p~n", [Todo1]), Todo2 = #todo{status=urgent, text="Fix me in one day at most!!!"}, io:format("~p~n", [Todo2]), Todo3 = Todo2#todo{status=done}, io:format("~p~n", [Todo3]).
提取记录字段(模式匹配)
-include("record_sample.hrl"). retrieval_filed() -> Todo1 = #todo{}, #todo{who=W, text=Txt} = Todo1, io:format("~p~n", [W]), io:format("~p~n", [Txt]).
映射组
Erlang映射概念上类似于Java中的Map、Python中的字典。
映射组从Erlang的R17版开始可供使用,在此不做记录(Ref [2] §5.3)。
二进制型
作为爱立信中诞生的语言,Erlang自然对网络通信协议栈中协议底层数据处理足够重视,但就当前而言,我倾向于考察Erlang在MPI(Message Passing Interface)所作的实践,故在此不做记录(Ref [2] §7,或许在Erlang分布式编程中有所涉及)。
2.2 程序结构
Erlang程序往往由几个模块构成,每个模块包含按逻辑组合在一起的函数。
模块是Elrang的基本代码单元。保存在.erl文件中,编译后才可执行,编译后的模块文件扩展名是.beam。
.beam文件中包含可以从其他任何函数调用的字节代码,.beam指Björn的抽象虚拟机。
模块
模块顶部有一些-attribute(Value)构成的属性列表,它们通常放在模块的开始。
可以编写自定义的模块属性,但只能有一个参数。
A sample:temp.erl
Emacs中Erlang -> Skeletons -> gen_server生成的代码片段(gen_server是一个OTP行为模式,后面记录OTP的文章中会涉及)
-module(temp).◄ 模块声明,声明中模块名必须与模块文件名相同 -behaviour(gen_server). %% API -export([start_link/0]). ◄ 导出声明 %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). -define(SERVER, ?MODULE). ◄ 宏声明 -record(state, {}). ◄ 记录声明 %%%=================================================================== %%% API %%%=================================================================== start_link() -> ◄ 函数定义 gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). %%%=================================================================== %%% gen_server callbacks %%%=================================================================== init([]) -> {ok, #state{}}. handle_call(_Request, _From, State) -> Reply = ok, {reply, Reply, State}. handle_cast(_Msg, State) -> {noreply, State}. handle_info(_Info, State) -> {noreply, State}. terminate(_Reason, _State) -> ok. code_change(_OldVsn, State, _Extra) -> {ok, State}. %%%=================================================================== %%% Internal functions %%%===================================================================
函数
► A meaningful sample
-module(geometry). -export([area/1]). area({rectangle, Width, Height}) -> ◄ 子句1 Width * Height; area({square, Side}) -> ◄ 子句2 Side * Side.
fun抽象(函数作为一种数据结构)
Erlang是一种函数式编程语言;函数是一种数据结构,就像元组和列表一样,可以作为函数的参数、返回值。
Erlang中函数数据类型称为fun,fun语法定义的函数是一类匿名函数,等价的描述是lambda抽象。
fun_basic() -> Double = fun(X) -> 2*X end, ◄ fun函数 io:format("~p~n", [Double(2)]). fun_with_clause() -> TempConvert = fun({c, C}) -> {c, 32 + C*9/5}; ({f, F}) -> {f, (F-32)*5/9} end, ◄ 带有子句的fun函数 io:format("~p~n", [TempConvert({c, 100})]), io:format("~p~n", [TempConvert({f, 212})]). fun_as_parameter() -> L = [1,2,3,4], lists:map(fun(X) -> 2 * X end, L). ◄ fun函数作为参数 fun_as_return_value() -> List = [a,b,c,d], TestListMembership = fun(L) -> (fun(X) -> lists:member(X, L) end) end, ◄ fun函数作为返回值 TestInList = TestListMembership(List), io:format("~p~n", [TestInList(a)]), io:format("~p~n", [TestInList(e)]). % Call sample - fun_spike:for(1,10, fun(X) -> X*X end). for(Max, Max, F) -> [F(Max)]; ◄ fun函数作为参数,定义基于列表的for循环结构 for(I, Max, F) -> [F(I) | for(I+1, Max, F)].
函数元数(arity)
函数的参数个数,元数也是函数签名的一部分。
惯用法:将名称相同、元数不同的函数用作辅助函数。
元调用(apply)
BIF apply语法:
apply(Mod, Func, [Arg1, Arg2, …, ArgN])
等价于Mod:Func(Arg1, Arg2, …, ArgN)
其中参数Mod、Func可以是动态计算出来的。
Mod可以是一个原子,一个元组,为元组时称为元组模块:
{Mod, P1, P2,…, Pn}:Func(Arg1, Arg2, …, ArgN])
等价于Mod:Func(Arg1, Arg2, …, ArgN, {Mod, P1, P2,…, Pn })
元组模块概念在此不做记录(Ref [2] §24.3)。
函数引用
函数名称形式引用,类似于指向函数的指针,但少了指针这个令人烦恼的玩意儿。
语法:
fun LocalFunc/Arity fun Module:RemoteFunc/Arity
包含模块名的函数引用提供了动态代码升级的切换点(热代码替换在次不做记录,Ref [1] §8, [2] §8.10)
编译和运行
Erlang Shell中目录切换和查看
> cd(“Path”).
> ls().
> pwd().
> c(ModuleName).
> c(ModuleName, [Option]).
其中Option可以是
export_all 测试用,导出所有函数
{I, Dir} 将Dir加入模块搜索路径
详细的说明见compile模块文档中 file(File, Options) -> CompRet的Options说明。
2.3 语法
表达式
Erlang中,任何可以执行并生成一个值的语法构造称为表达式。
表达式序列
是一些由,分隔的表达式:E1, E2, …, En,其值为En的值。
块表达式
处理代码中Erlang语法要求单个表达式,但想使用一个表达式序列情况。
语法:
begin Expr1, Expr2, …, ExprN end
其值为ExprN的值。
算术表达式
+X
-X
X * Y
X / Y (浮点数除法)
bnot X (按位取反)
X div Y (整除)
X rem Y (取余数)
X band Y (按位与)
X + Y
X - Y
X bor Y (按位或)
X bxor Y (按位异或)
X bsl N (将X向左算术位移N位)
X bsr N (将X向右算术位移N位)
优先级问题可以通过可爱的()解决,出于完整性考虑,记录如下:
高
低 |
操作符 |
结合性 |
: |
||
# |
||
一元+、-、bnot、not |
||
/、*、div、rem、band、and |
左结合 |
|
+、-、bor、bxor、bsl、bsr、or、xor |
左结合 |
|
++、-- |
右结合 |
|
==、/=、=<、<、>=、>、=:=、=/= |
||
andalso |
||
orelse |
||
=、! |
右结合 |
|
catch |
操作符
列表操作:++ --
L1 ++ L2:使L2附加到L1上
L1 -- L2:从L1中移除所有同时在L2出现的元素一次且仅一次。
模式的匹配操作符
惯用法:将模式指派个一个临时变量,便于随后语句中重建数据类型;
匹配操作符可以用于模式里的任意位置,即支持模式匹配的嵌套。
分支
关卡(guard)
概念:关卡对某个模式中的变量执行简单的测试与比较。可以在函数头部使用(使用when关键字)、也可以在支持表达式的任何地方使用,用作表达式时,其执行结果为原子true或false。
语法
关卡由用,分隔的关卡表达式序列组成:GuardExpr1, GuardExpr2, …, GuardExprN
关卡序列由用;分隔的关卡序列组成:G1;G2;…;Gn
,的含义等价于逻辑与,而;等价于逻辑或。
语义
关卡表达式是合法Erlang表达式集合的一个子集,要求执行无副作用、不能使用用户定义的函数。
合法的关卡表达式:
原子true
其他常量,在关卡表达式中会成为false
关卡判断函数、一些内置函数(Ref [2] §4.7.2)
数据结构比较(Ref [2] §8.24)
算术表达式
布尔表达式
短路布尔表达式
case表达式
语法
case Expression of Pattern1 [when Guard1] -> Expr_sequence1; Pattern2 [when Guard2] -> Expr_sequence2; … end
语义
Expression执行的值Value与Patterni模式匹配,执行第一个匹配的表达式序列,该表达式序列结果作为case语句的结果;没有匹配则发生异常。
if表达式
语法
if Guard1 -> Expr_sequence1; Guard2 -> Expr_sequence2; … end
语义
依次执行关卡Guardi,如果关卡执行结果为true则其后表达式序列的结果作为if表达式的结果;如果所有关卡执行结果均为false则发生异常。
惯用法:最后一个关卡是true。
错误处理
生成错误的BIF
eixt(Why) 想终止当前进程时使用,如果该错误没有被捕获,信号{‘EXIT’, Pid, Why}会广播到所有与当前进程连接的进程;
throw(Why) 抛出函数调用者可以捕获的错误;
error(Why) 产生崩溃性错误,调用者往往无法处理的严重性错误。
try…catch表达式
语法
try FuncOrExpr_Sequence of Pattern1 [when Guard1] -> Expr_Sequence1; Pattern2 [when Guard2] -> Expr_Sequence2; … catch ExceptionType1: ExPattern1 [when ExGaurd1] -> ExExpr_Sequence1; ExceptionType2: ExPattern2 [when ExGaurd2] -> ExExpr_Sequence2; … after AfterExpr_Sequence end
其中,ExceptionTypei ::= throw | exit | error,默认是throw
简写法
try F catch … end
语义
执行FuncOrExpr_Sequence过程中,
如果没有抛出异常,其结果与Patterni模式匹配,如果匹配,则Patterni后的Expr_Sequencei结果作为try…catch表达式的结果;
如果抛出异常,异常与ExPatterni模式匹配,如果匹配,则ExExpr_Sequencei的结果作为try…catch表达式的结果。
AfterExpr_Sequence总会被执行,结果被丢弃。
catch表达式
异常如果发生在catch语句中,会被转换成一个描述此错误的{‘EXIT’, …}元组。
杂项
暂无。
2.4 终结者
类型系统
Erlang的类型表示法,用于定义新的数据类型、提供类型注解。
在从数据类型名称或变量名称无法“推断”出其语义时,很有用。
类型表示法
类型定义,语法:
Type1 :: A | B | C …
定义新的类型(类型声明,type declaration),语法:
-type NewTypeName(TVar1, TVar2, …, TVarN) :: Type.
其中,TVari是可选的类型变量,Type是类型表达式。
函数类型规范(type specification),语法:
-spec functionName(T1, T2, …, Tn) -> Tret when
Ti :: Typei,
Tj :: Typej,
…
其中,Tk是参数的类型,Tret是返回值的类型。
一些预定义的类型及其含义
any() 任意Erlang数据类型 X() 类型为X的Erlang对象 none() 永不返回的函数类型 [X] 由类型X组成的列表 {T1, T2, …, Tn} 由类型Ti组成的元组,大小为n -type term() :: any(). -type boolean :: true | false. -type byte :: 0..255. -type char():: 0..16#10ffff. -type number():: integer() | float(). -type list():: [any()]. -type string():: [char()]. -type nonempty_string():: [char(), …]. 由字符类型构成的非空列表 -type iolist :: maybe_improper_list(byte() | binary() | iolist(), binary() | []). -type maybe_improper_list() :: maybe_improper_list(any(), any()). -type maybe_improper_list(T) :: maybe_improper_list(T, any()). -type module() :: atom(). -type mfa():: {atom(), atom(), atom()}. -type node():: atom(). -type timeout() :: infinity | non_neg_integer(). -type no_return() :: none(). |
函数类型规范实例,摘自lists:foldl
foldl(Fun, Acc0, List) -> Acc1 Types: Fun = fun((Elem :: T, AccIn) -> AccOut) Acc0 = Acc1 = AccIn = AccOut = term() List = [T] T = term() Calls Fun(Elem, AccIn) on successive elements A of List, starting with AccIn == Acc0. Fun/2 must return a new accumulator which is passed to the next call. The function returns the final value of the accumulator. Acc0 is returned if the list is empty. For example: > lists:foldl(fun(X, Sum) -> X + Sum end, 0, [1,2,3,4,5]). 15 > lists:foldl(fun(X, Prod) -> X * Prod end, 1, [1,2,3,4,5]). 120 |
至于类型推断和分析工具(TypEr、dialyzer),在此不做阐述(Ref [1] §18 [2] §9.3)。
EDoc
EDoc可以从内嵌在注释中格式为@tag text的标签生成文档。
模块级标签
@author
@copyright
@doc
@reference
函数级标签
@spec
@doc
@see
@throws
@deprecated
@hidden
@private
@since
通用级标签
@type
@todo
运行EDoc
EDoc函数在edoc模块中,运行命令示例:
> edoc:files([“sample1.erl”, “sample2.erl”]).
EUnit
crashdump_viewer
dbg
垃圾收集
3 杂项
获取环境
(erts-5.10.1应用中的init)
> init:get_argument(root). %Erlang/OTP的安装目录
> init:get_argument(progname). %启动Erlang的程序名称
> init:get_argument(home). %用户系统中主目录
USER_HOME = C:/Users/lenovo
> init:get_arguments(). %所有参数
.erlang文件
REF DOC_ROOT/erts-5.10.1/doc/html/erl.html section Configuration
查看加载模块
> code:get_path().
Sample:
节点连通/Cookie
.erlang.cookie在USER_HOME目录下。
> net_adm:ping(<Node>).
> auth:set_cookie(<XXX>).
图形化监测工具
Appmon
> appmon:start().
WebTool版Appmon
> webtool:start().
Pman(process manager)
> pman:start().
Debugger
$ erlc +debug_info –o ebin src/*.erl
> debugger:start().
表查看器
> tv:start().
工具栏
> toolbar:start().
参考文献
[1] Cesarini F., Thompson S.著,慕尼黑Isar工作组 杨剑译.
Erlang编程指南.
北京: 机械工业出版社.2011.
[2] Armstrong J.著,牛化成 译.
Erlang程序设计(第2版).(Programming Erlang, Second Edition – Software for a Concurrent World).
北京: 人民邮电出版社.2014.