Erlang 101 Erlang环境和顺序编程

笔记系列

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

Erlang 101 Erlang环境和顺序编程

Erlang 101 Erlang环境和顺序编程

0 范围

环境

Erlang运行时环境、Emacs开发环境

顺序编程

数据结构

语法和语义

1 Erlang环境搭建

Erlang website

http://www.erlang.org/

实践版本

otp_win32_R16B/erl5.10.1

安装路径记为ERLANG_HOME=D:/erl5.10.1

获取环境变量的方法见杂项一节。

Javaer Eclipse

Erlang plugin (ErlIDE) for Eclipse

http://erlide.org/index.html

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变量不会变,或者称为一次性赋值变量

赋予了值的变量称为绑定变量,否则称为未绑定变量

Erlang 101 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:

Erlang 101 Erlang环境和顺序编程

节点连通/Cookie

.erlang.cookieUSER_HOME目录下。

> net_adm:ping(<Node>).

> auth:set_cookie(<XXX>).

图形化监测工具

Appmon

> appmon:start().

WebToolAppmon

> 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.

上一篇:《selenium2 python 自动化测试实战》(17)——几个cookies操作


下一篇:设置阿里云maven*仓库的settings.xml