主要是看《erlang程序设计》这本,把其中语法点记录下来,备忘。
注释
% 打头
%%% 一般用于模块注释
%% 用于函数注释
数字
erlang中整数是不定长的,没有误差。要多大有多大。
可以用N进制写法来表示一个数字:16#FE34 8#377
ASC码可以这么得到: $a 就是数字97, $\^c 就是3, $\n 就是10。
浮点数好像是有精度的。最大是 999999999999999.9,再大1位,就变成1.0e16了。
变量
必须大写字母或者下行线开头。
变量只能被赋值一次,赋值前称自由变量。
一个下划线“_”表示匿名变量,其实是一个占位符,不是变量,所以它能被多次赋值,但是值没法用。
书上建议是临时变量(为编程需要,用一次就扔掉的那种变量)用下行线加大写字母开头,比如_Count,以后如果这个变量变成有用的变量了,那么把下行线去掉就行了。
原子(atom)
原子是全局有效的。(全不全局好像没意义啊???)
原子是一串以小写字母开头,后跟数字字母或下划线(_)或邮件符号(@)的字符。
使用单引号引起来的字符也是原子。使用这种形式,我们就能使得原子可以用大写字母作为
开头或者包含非数字字符。例如,'Monday'、'Tuesday'、'+'、'*'、'an atom with spaces'。你还可
以将原本不需要使用引号的原子引起来,'a'实际上就等同于a。
一个原子的值就是原子自身。
元组(tuple)
将若干个以逗号分割的值用一对花括号括起来,就形成了一个元组;
为了方便记忆,通常可以使用一个原子作为元组的第一个元素来标明这个元组所代表的含义。因此我们可以用{point, 10, 45}。
元组可以嵌套。
1>Person = {person,
{name, joe},
{height, 1.82},
{footsize, 42} }.
可通过模式匹配来提取元组中某个字段的值到变量:
2>{_, {_, Who},{_,_}, {_,_} }=Person.
执行后,Who 将等于 joe。
在声明元组时,就自动创建了元组,不再使用它们时,元组也随之销毁。Erlang使用垃圾搜集器去收回没有使用的内存,因此我们不用担心内存分配的问题。
注意:不能采用未被赋值的自由变量来组建元组,否则会报错。
注意:“=”不是逻辑等,也不是赋值号,是模式匹配。如果符号左右有自由变量,总是尝试给自由变量赋值来让匹配能成功,所以具有了赋值功能。
列表(list)
将若干个以逗号分割的值用一对方括号括起来,就形成了一个列表。
列表之中的各个元素可以有各自不同的类型(看不出为何需要元组和列表两个基础类型???)。
术语:列表的第一个元素称为列表的头(head),头可以是任何类型、
从列表中移除头,所剩下的东西就称为列表的尾(tail),尾总是一个列表。
如果T是一个列表,那么[H|T]也是一个列表 ,这个列表以H为头,以T为尾。竖线符号(|)可以将列表的头和尾分隔开来。
[]则是空列表。
可通过模式匹配提取列表元素:
1>[X, Y | L2 ] = L. %从列表中提取列表头放入X,第二个元素放入Y,其余放入L2。
1)字符串
erlang实际上没有字符串,字符串其实就是整数列表,每个字节是一个元素。
字符串只能用双引号(")括起来。
字符串中的字符是Latin-1(ISO-8859-1)编码的字符。
Eshell打印一串列表值时,只有列表中的所有整数都是可打印字符,它才把这个列表当作字符串来打印:
1>[1,2,3]
[1,2,3]
2>[83, 117, 114, 112]
"Surp"
可以通过 $ 号来获得一个字符的ASCII码,如下:
1> A=$A.
65
2> B=$a.
97
模块
erlang中的基本代码单元。模块是线性的,没有树状组织功能,因此工程大了很痛苦,还好后来出来了package的概念。
模块的代码后缀名为erl,编译后后缀为beam。模块的名称必须为atom(小写字母开头,可以包含字母数字,@, _,否则需要用 " 将atom包含起来)。
举例:
%%%geometry.erl
-module(geometry).
-export([area/1]). %表示导出area函数,该函数有1个参数。
-compile(export_all). %表示导出所有的函数
文件名和模块名必须相同,模块名不能和系统模块名重名。
编译,成功后在相同目录产生一个文件名相同,后缀是beam的文件:
$ > erl
1 > c(geometry).
{ok, geometry}
函数
erlang是通过模块,函数名称和参数个数来唯一确定一个函数,在书中参数个数称为arity。
在同一个模块内,如果没有指明模块,则默认为本模块或者通过import 属性导入的模块,因此在erlang的编程规范中,建议不要使用import,因为会引起混淆。
函数名称相同,参数个数不同的,认为是不同函数,他们之间没有任何联系。
“->”前的称函数头,后的陈函数体,一个函数可以由多个子句构成,用分号分隔。实际执行时执行调用参数和函数头匹配成功的那个子句(注:空列表和非空列表是不能匹配的)。
每个函数子句可以由多个表达式组成,表达式之间用逗号分隔。
-module(geometry).
area({rectangle, Width, Height}) -> Width * Height;
area({circle, R}) -> math:pi() * R * R;
area({square, X}) -> X * X.
函数的匹配是从文件的开始向下进行的,所以限制最严格的必须放上面。
1)函数引用
2> geometry:area({circle,10}).
314.1592653589793
3> A=fun geometry:area/1. %fun geometry:area/1 表示geometry模块的参数个数为1个的area函数#Fun<geometry.area.1>
4> A({circle,10}).
314.1592653589793
5>
匿名函数(fun)
2> Z=fun(X)->X*X end.
#Fun<erl_eval.6.82930912>
也可以有多个函数子句:
4> TempConvert = fun({c,C}) -> {f, 32 + C*9/5};
4> ({f,F}) -> {c, (F-32)*5/9}
4> end.
#Fun<erl_eval.6.82930912>
5> TempConvert(c, 30).
** exception error: interpreted function with arity 1 called with two arguments
6> TempConvert( {c,30} ).
{f,86.0}
7> TempConvert( {f,77} ).
{c,25.0}
返回匿名函数的函数
1> Fruit = [apple,pear,orange].
[apple,pear,orange]
2> MakeTest = fun(L) -> (fun(X) -> lists:member(X, L) end) end.
#Fun<erl_eval.6.56006484>
3> IsFruit = MakeTest(Fruit).
#Fun<erl_eval.6.56006484>
4> IsFruit(pear).
true
5> IsFruit(apple).
true
6> IsFruit(dog).
false
1)建立自己的for循环
for(Max, Max, F) -> [F(Max)];
for(I, Max, F) -> [F(I)|for(I+1, Max, F)].
对这个例子而言,计算 for(1,10,F) 就是建立一个列表 [F(1), F(2), ..., F(10)].
列表的解析
[ F(X) || X <- L]
上式返回一个列表,它的每个元素是L列表中每个元素经F函数计算后的结果。
更复杂的写法:
1> [ X || {a, X} <- [{a,1},{b,2},{c,3},{a,4},hello,"wow"]]. %这个例子隐式包含了过滤功能
[1,4]
[X || X <- T, X < 100] %这个例子显示使用了过滤子。
要理解列表解析,看这个毕达哥拉斯三元组的生成函数样例就好了。pythag函数生成A2+B2=C2且A+B+C要小于等于N的所有满足条件的数值。
pythag(N) ->
[ {A,B,C} ||
A <- lists:seq(1,N),
B <- lists:seq(1,N),
C <- lists:seq(1,N),
A+B+C =< N,
A*A+B*B =:= C*C
].
心得:列表和模式匹配操作应该是erlang的核心了,列表可以跟数据库表等价来理解,有了列表解析语法,相当于可以很方便的对表的每条记录施加一个函数操作,当然也可以过滤掉不需要的记录。
断言(guard)
用于强化模式匹配功能的结构,如下通过断言实现了取二者中大值的函数。
max(X, Y) when X > Y -> X;
max(X, Y) -> Y.
when X>Y, X>10, Y>5 %逗号分隔,表示三个表达式之间是 and 关系
when X>Y; X>10; Y>5 %分号分隔,表示三个表达式之间是 or 关系
断言中能使用的断言谓词如下:
is_atom(X) X is an atom. is_binary(X) X is a binary. is_constant(X) X is a constant.
is_float(X) X is a float. is_function(X) X is a fun.
is_function(X, N) X is a fun with N arguments.
is_integer(X) X is an integer.
is_list(X) X is a list.
is_number(X) X is an integer or a float. is_pid(X) X is a process identifier. is_port(X) X
is a port.
is_reference(X) X is a reference.
is_tuple(X) X is a tuple.
is_record(X,Tag) X is a record of type Tag.
is_record(X,Tag,N) X is a record of type Tag and size N
断言中能使用的BIF函数如下:
abs(X) Absolute value of X.
element(N, X) Element N of X. Note X must be a tuple.
float(X) Convert X, which must be a number, to a float.
hd(X) The head of the list X. length(X) The length of the list X. node() The current node.
node(X) The node on which X was created. X can be a process.
An identifier, a reference, or a port.
round(X) Converts X, which must be a number, to an integer.
self() The process identifier of the current process.
size(X) The size of X. X can be a tuple or a binary.
trunc(X) Truncates X, which must be a number, to an integer.
tl(X) The tail of the list X.
1)短路断言表达式 orelse,andalso
书中还提到 orelse 和 andalso
他们跟or and 的区别是,or和and会对表达式的左右两边都求值,然后做判断,
orelse和andalso从左到右求值,遇到表达式值能确定时,就终止,右边不一定有求值的机会,所以如果右边表达式有错,也不一定会暴露。
记录(record)
记录本质上就是元组。当元组的元素很多的时候,每个元素有个名字,是很方便的,所以有了记录。
记录定义放在后缀是 (.hrl) 的文件中。
-record(Name, {
%% the next two keys have default values
key1 = Default1,
key2 = Default2,
...
%% The next line is equivalent to
%% key3 = undefined key3,
key3,
...
}).
代码中可以用 -include("。。。.hrl"). 来引入记录定义,文件名可以是绝对路径或者相对路径。
Eshell不识别记录语法。 要用如下命令把记录定义引入:
1>rr("todo.hrl").
%%如下是记录的创建和变更语法:
2> X=#todo{}.
#todo{status = reminder,who = joe,text = undefined}
3> X1 = #todo{status=urgent, text="Fix errata in book"}.
#todo{status = urgent,who = joe,text = "Fix errata in book"}
4> X2 = X1#todo{status=done}.
#todo{status = done,who = joe,text = "Fix errata in book"}
记录元素的提取-1
5> #todo{who=W, text=Txt} = X2.
#todo{status = done,who = joe,text = "Fix errata in book"}
6> W.
joe
7> Txt.
"Fix errata in book"
记录元素的提取-2
8> X2#todo.text.
"Fix errata in book"
判断记录类型
do_something(X) when is_record(X, todo)
记录是元组的伪装
% rf函数 从Eshell中卸掉记录的定义,之后你再查看记录变量,看到的就是元组。
11> X2.
#todo{status = done,who = joe,text = "Fix errata in book" }
12> rf(todo).
ok
13> X2.
{todo,done,joe,"Fix errata in book" }
BIF
BIF = build-in-Function, 内建的函数,erlang语言无法实现的功能,通常用BIF来实现,比如
1> tuple_to_list({12,cat,"hello"}).
[12,cat,"hello"]
2> time().
{20,0,3}
所有的BIF其实是属于 erlang 模块的,但是会自动导入,所以我们不需要写 erlang:time().
比特语法
1)二进制数据结构
就是字节流,所以每个元素必须是 0~255 之间。 跟字符串的区别,我想主要是容量大小方面,二进制结构适合放很大量的数据。
举例:<<1, 100, 255>> <<"hello">>
2) 二进制数据结构的BIF
list和bin的相互转换: list_to_binary
erlang数据和bin的相互转换: term_to_binary
3)比特语法
<<>>
<<E1,E2,...,En>>
每个元素Ei指定了二进制对象的一个字段。每个元素Ei有四种格式的可能:
Ei=Value |
Value:Size |
Value/TypeSpecifierList |
Value:Size/TypeSpecifierList
Size必须是一个得到整数的表达式。在模式匹配中,Size必须是整数或者值为整数的变量。Size
不可以是尚未绑定的变量。
Size的值指定了数据段的单元数。缺省值依赖于类型。对整数缺省为8,浮点数缺省为64,而
二进制对象则对应其长度。在模式匹配时,缺省值仅对最后一个元素有效。其他所有匹配时的二
进制对象元素长度必须指定。
TypeSpecifierList是以连字符分割的一列元素,形式为End-Sign-Type-Unit。任何前述元素都可
以省略,元素也可以在任何顺序。如果一个元素被省略,就使用其缺省值。
@type End=big | little | native
这是指定机器的字节序,native是运行时检测,依赖于具体的CPU。缺省是big。
@type Sign=signed | unsigned 这个参数仅用于模式匹配,缺省是unsigned
@type Type=integer | float | binary 缺省是integer
@type Unit=1 | 2 | … 255
这个段的总单位数,这个单位数必须大于等于0,而且必须是8的整倍数。
Unit的缺省值依赖于Type,如果Type是integer 则为1,如果Type是binary则为8。
举例:
1> Red=2.
2
2> Green=61.
61
3> Blue=20.
20
4> Mem=<<Red:5,Green:6,Blue:5>>. %按位构造二进制流
<<23,180>>
5> <<R1:5,G1:6,B1:5>>=Mem. %按位解析二进制流
<<23,180>>
6> R1.
2
1> {<<16#12345678:32/big>>,<<16#12345678:32/little>>, <<16#12345678:32/native>>,<<16#12345678:32>>}.
{<<18,52,86,120>>,
<<120,86,52,18>>,
<<120,86,52,18>>,
<<18,52,86,120>>}
溢出的情况(我想都是从低位开始取,这台机器的native是little的,如果是big,估计结果会不同)
7> <<16#1234:8/little>>.
<<"4">>
8> <<16#1234:8/big>>.
<<"4">>
9> <<16#1234:4/big>>.
<<4:4>>
10> <<16#1234:4/little>>.
<<4:4>>
11>
宏
在erlang程序设计的5.4.14章节。