Hello World
%% hello.erl
-module(hello).
-export([start/0]).
start() ->
io:format("Hello World~n").
上述文件中,
-module(hello).
指的是此文件包含用于hello模块的代码。它应该与文件名一致(除了.erl这个文件拓展名)。模块必须以一个小写字母开头。模块名属于一个原子。
--export([start/0]).
表示没有参数的函数start可以在模块外调用(类似于Java中的public)。如果想导出多个函数,使用以下语法:
-export([function1/N1, function2/N2, ...]).
Erlang Shell
可在控制命令行输入erl
进入Erlang shell,用halt()
方法退出。
在Erlang shell中,c(moduleName).
命令编译moduleName.erl
文件里的代码,并生成moduleName.beam
文件。例如:
PS E:\ErlangFile> erl
Eshell V8.3 (abort with ^G)
1> c(hello).
{ok,hello}
2> hello:start().
Hello World
ok
3> halt().
PS E:\ErlangFile>
在Erlang shell外,编译和运行如下:
$ erlc moduleName.erl
$ erl -noshell -s hello start -s init stop
erl -noshell -s hello start -s init stop
命令加载了hello
模块并执行hello:start()
函数。随后,执行了init:stop()
终止了Erlang对话。
PS E:\ErlangFile> erlc hello.erl
PS E:\ErlangFile> erl -noshell -s hello start -s init stop
Hello World
PS E:\ErlangFile>
Erlang基础
1. 注释
Erlang里的注释从一个百分号字符(%
)开始,一直延伸到行尾。Erlang没有块注释。
2. 数字
Erlang里的数字不是整数就是浮点数。
- 整数
整数的运算是精确的,而且用来表示整数的位数只受限于可用的内存。
整数可以有三种不同的写法。
- 传统写法
- K进制整数 除10以外的数字进制整数使用
K#Digits
这种写法。
1> 16#11.
17
2> 32#12.
34
$
写法$C
这种写法代表了ASCII字符C的整数代码。
- 浮点数
一个浮点数由五部分组成:一个可选的正负号,一个整数部分,一个小数点,一个分数部分和一个可选的指数部分。
1> 4/2.
2.0
2> 5/3.
1.6666666666666667
3. 变量
- Erlang中的变量以大写字母开头。
- 可用"
=
"操作符给变量赋值,严格来说是给变量绑定一个值。因为在Erlang中,变量一旦绑定了值,就不允许重新绑定。“=
” -> 模式匹配符。 - 以小写字母开头的不是变量而是符号常量,称为原子。
4. 原子(atom)
- 在Erlang中,原子是全局性的,而且是不需要宏定义和包含文件即可实现的。
- 原子以小写字母开头,后接一串字母、数字、下划线、@符号。原子还可以放在单引号( ’ )中。可以用这种方式创建以大写字母开头的或者包含其他符号的原子,如
'Monday'
,'*&^123'
。甚至可以给无需引号的原子加上引号,因此'a'
和a
的意思是完全一致的。
5. 元组(tuple)
元组,用于保存固定数量的元素。创建元组的方法就是用大括号{}
把想要表达的值括起来,并用逗号分隔他们。
6. 列表(list)
列表,被用来存放任意数量的事物。创建列表的方法是用中括号[]
把列表元素括起来,并用逗号分隔开。
7. 字符串
严格来说,Erlang里没有字符串。要在Erlang中表示字符串,可以选择一个由整数组成的列表或者二进制型。当字符串表示为一个整数列表时,列表里的每个元素都代表了一个Unicode代码点。
可以用字符串字面量来创建一个列表。字符串字面量其实就是用双引号围起来的一串字符。
1> Name = "Hello".
"Hello"
"Hello"其实是一个列表的简写,这个列表包含了代表字符串里各个字符的整数字符代码。
当shell打印某个列表的值时,如果列表里的所有整数都代表可打印字符,他就会将其打印成字符串字面量。否则打印成列表记法。
Eshell V8.3 (abort with ^G)
1> [1,2,3].
[1,2,3]
2> [115,117,114,101].
"sure"
3> [1,115,117,114,101].
[1,115,117,114,101]
另外,$a
代表字符a的整数。
Eshell V8.3 (abort with ^G)
1> I = $s.
115
2> [I-32,$u,$r,$e].
"Sure"
用列表来表示字符串时,它里面的各个整数都代表Unicode字符。必须使用特殊格式的语法才能输入某些字符,在打印列表时也需要选择正确的格式惯例。
Eshell V8.3 (abort with ^G)
1> X = "a\x{221e}b".
[97,8734,98]
2> io:format("~ts~n", [X]).
a\x{221E}b
ok
如果shell将某个整数列表打印成字符串,而你想打印成一列整数则需要使用格式化:
Eshell V8.3 (abort with ^G)
1> X = [97,98,99].
"abc"
2> io:format("~w~n", ["abc"]).
[97,98,99]
ok
模块
模块是Erlang的基本单元。模块保存在扩展名为.erl
的文件里,而且必须先编译才能运行模块里的代码。编译后的模块以.beam
作为扩展名。
-module(bit_test).
-compile(export_all).
-mytags("This is my tags").
reverse_bitstring_by_byte(BitString) ->
ByteList = [Byte || <<Byte:8>> <= BitString],
ReverseByteList = lists:reverse(ByteList),
ReverseBitString = list_to_binary(ReverseByteList),
ReverseBitString.
reverse_bitstring_by_bit(BitString) ->
BitList = [Bit || <<Bit:1>> <= BitString],
ReverseBitList = lists:reverse(BitList),
ReverseBitString = list_to_binary(ReverseBitList),
ReverseBitString1 = << <<Bit:1>> || <<Bit:8>> <= ReverseBitString>>,
ReverseBitString1.
模块属性
模块属性的语法是-AtomTag(...)
,它们被用来定义文件的某些属性。有两种类型:预定义型+用户定义型。
- 预定义型:
- -module(modname).
这是模块声明。modname
必须是一个原子。此属性必须是文件里的第一个属性。按照惯例,modname
的代码应当保存在名为modname.erl
的文件里。如果不这么做,自动代码加载就不能正常工作。 - -import(Mod, [FuncName1/ArityNum1, FuncName2/ArityNum2]).
import
声明列举了哪些函数需要导入到模块中。上面这个声明的意思是要从Mod
模块导入参数数量为ArityNum1
的FuncName1
函数,参数数量为ArityNum2
的FuncName2
函数。
一旦从别的模块里导入了某个函数,调用它的时候就无需指定模块名了。 - -export([FuncName1/ArityNum1, FuncName2/ArityNum2]).
导出当前模块里的FuncName1/ArityNum1
和FuncName2/ArityNum2
函数。函数只有被导出后才能在模块之外调用。 - -compile(Options).
-compile(export_all).
这个编译器选项经常会在调试程序时用到。它会导出模块里的所有函数,无需再显式使用-export
标识了。 - -vsn(Version).
指定模块的版本号。Version可以是任何字面数据类型。Version的值没有什么特别的语法或含义,但可以用于分析程序或者作为说明文档使用。
- 用户定义型:
语法:-TagName(TagValue).
TagName
必须是一个原子,而TagValue
必须是一个字面数据类型。模块属性的值会被编译进模块,可以在运行时提取。
1> bit_test:module_info().
[{module,bit_test},
{exports,[{reverse_bitstring_by_byte,1},
{reverse_bitstring_by_bit,1},
{module_info,0},
{module_info,1}]},
{attributes,[{vsn,[126186496540910565414291993722642793567]},
{mytags,"This is my tags"}]},
{compile,[{options,[]},
{version,"7.0.4"},
{source,"c:/Users/admin/Desktop/bit_test.erl"}]},
{md5,<<94,238,162,158,52,246,232,201,30,189,181,39,27,
156,132,95>>}]
2> bit_test:module_info(exports).
[{reverse_bitstring_by_byte,1},
{reverse_bitstring_by_bit,1},
{module_info,0},
{module_info,1}]
module_info()
返回一个属性列表,内含所有与被编译模块相关的元数据。module_info(X)
(X可以是exports
、imports
、attributes
和compile
中的一个)会返回与模块相关的单个属性。
fun:基本的抽象单元
Erlang是一种函数式编程语言。函数式编程语言表示函数可以被用作其他函数的参数,也可以返回函数。操作其它函数的函数称为高阶函数,而在Erlang中用于代表函数的数据类型称为fun
。fun
是“匿名的”函数。
1> Double = fun(X) -> 2*X end.
#Fun<erl_eval.6.118419387>
2> Double(2).
4
fun
可以有任意数量的参数:
3> Hypot = fun(X,Y) -> math:sqrt(X*X + Y*Y) end.
#Fun<erl_eval.12.118419387>
4> Hypot(3,4).
5.0
fun
也可以有多个不同的子句:
5> TempConvert = fun({c, C}) -> {f, 32 + C*9/5};
({f, F}) -> {c, (F-32)*5/9}
end.
#Fun<erl_eval.6.118419387>
6> TempConvert({c, 100}).
{f,212.0}
7> TempConvert({f, 212}).
{c,100.0}
注意:第5行的表达式占据了多行。输入这个表达式时,每输入一个新行,shell就会重复5>
这个提示符,意思是这个表达式还不完整,shell想得到更多的输入。
- 以fun作为参数的函数
标准库lists模块中有2个较为有用的以fun
作为参数的函数。
lists:map(F, L)
:这个函数返回的是一个列表,他通过给列表L里的各个元素应用fun F
生成。
3> lists:filter(fun(X) -> (X rem 2) =:= 0 end, [1,2,3,4,5,6,7,8,9]).
[2,4,6,8]
lists:filter(P, L)
,他返回一个新的列表,内含L
中所有满足条件P
的元素,即对于元素E
,P(E)
为true
。
filter
和map
等函数能够在一次调用里对整个列表执行某种操作,称为“一次一列表式操作”
lists:member(X, L)
,当X
是是列表L
中的成员,返回true
,否则返回false
。
- 返回fun的函数
1> Double = fun(X) -> 2*X end.
#Fun<erl_eval.6.118419387>
2> Double(5).
10
括号里的代码就是该函数的返回值,试着将fun
放到一个括号里。
记住,括号里的东西就是返回值。
3> Mult = fun(Times) -> (fun(X) -> X*Times end) end.
#Fun<erl_eval.6.118419387>
括号里的fun
是fun(X) -> X*Times end
,他只是一个关于X
的函数。Times
则是“外部”fun
的参数。
Mult(3)
执行后返回fun(X) -> X*3 end
,即内部fun
的主体(Times
被替换为3)
4> Triple = Mult(3).
#Fun<erl_eval.6.118419387>
5> Triple(3).
9
所以,Mult
是通用化的Bouble
。他不计算值,只是返回一个函数,调用该函数时会计算所需的值。
到目前为止,可以利用模式匹配和高阶函数编写简单的for循环:
%% lib_misc.erl
for(Max, Max, F) -> [F(Max)];
for(I, Max, F) -> [F(I) | for(I+1, Max, F)].
1> lib_misc:for(1, 10, fun(I) -> I end).
[1,2,3,4,5,6,7,8,9,10]
2> lib_misc:for(1, 10, fun(I) -> I*I end).
[1,4,9,16,25,36,49,64,81,100]
利用sum和map编写total函数:
%% shop1.erl
-module(shop1).
-export([total/1]).
-import(lists, [map/2,sum/1]).
total(L) ->
sum(map(fun({What, N} -> shop:cost(What) * N end, L)).
-import(lists, [map/2,sum/1]).
声明意思是map/2
是从lists
模块导入的。意味着我们可以用map(Fun,...)
代替lists:map(Fun,...)
%% shop.erl
-module(shop).
-export([cost/1])
cost(oranges) -> 5;
cost(newspaper) -> 8;
cost(pears) -> 2;
cost(apples) -> 9;
cost(milk) -> 7.
1> Buy = [{oranges,4},{newspaper,1},{apples,10},{pears,6},{milk,3}].
[{oranges,4},{newspaper,1},{apples,10},{pears,6},{milk,3}]
2> L1 = lists:map(fun({What, N} -> shop:cost(What) * N end, Buy).
[20,8,20,54,21]
3> lists:sum(L1).
123
4> shop1:total(Buy).
123
关卡
关卡guard
是一种结构,用于增加模式匹配的威力。通过关卡可以实现对某个模式里的变量执行简单的测试和比较。例如:
计算X
,Y
间的最大值:
max(X,Y) when X > Y -> X;
max(X,Y) -> Y.
可以在函数定义里的头部使用关卡(通过when
关键字引入),也可以在支持表达式的任何地方使用。
关卡序列:是指单一或一系列的关卡,用分号;
分隔。对于关卡序列G1;G2;...;Gn
,只要其中一个关卡为true
,那么它的值为true
。
关卡:由一系列关卡表达式组成,用逗号,
分隔。关卡G1,G2,...,Gn
只有在所有的关卡表达式都为true
时为true
。
合法的关卡表达式是所有合法Erlang表达式的子集。
编写关卡时,可利用短路或orelse
、短路与andalso
来提高效率(优先级:and > or > andalso > orelse
)。
Erlang条件句式:case和if表达式
1. case表达式
case
的语法如下:
case Expression of
Pattern1 [when Guard1] ->Expr_seq1;
Pattern2 [when Guard2] ->Expr_seq2;
...
end
首先Expression
被执行,得到值Value
,Value
依次与Pattern
匹配,直到匹配成功并执行相应地表达式序列,其值为case
表达式的值。否则发生异常。
例如:
filter(P, [H | T]) ->
case P(H) of
true -> [H | filter(P, T)];
false -> filter(P, T)
end;
filer(P, []) ->
[].
返回一个列表,内含L
即[H | T]
中符合条件P
的元素X
.
2. if表达式
if的语法如下:
if
Guard1 ->
Expr_seq1;
Guard2 ->
Expr_seq2;
...
end
首先执行Guard1
,如果得到的值是true
,则if表达式的值就是表达式序列Expr_seq1
的值,否则执行Guard2
,以此类推,直至某个Guard
为true
时。
if表达式必须至少有一个关卡的执行结果为true
,否则就会发生异常。
一般情况下,if表达式的最后一个关卡是原子true
。
Erlang基础补充
1. Erlang shell里的内建命令
-
pwd()
打印当前工作目录 -
ls()
列出当前目录里的所有文件名 -
cd(Dir)
修改当前工作目录值Dir -
f()
能够让shell忘记现有的任何绑定。
Eshell V8.3 (abort with ^G)
1> X = 1.
1
2> X = 2.
** exception error: no match of right hand side value 2
3> f().
ok
4> X = 2.
2
2. 关闭shell的方法
CTRL+C
,会立即停止系统,可能导致数据损坏q()
,受控关闭,所有打开的文件都会被刷入缓存并关闭,数据库(正在运行的)会被停止,所有应用程序都会以有序的方式关停。q()
是init:stop()
命令在shell里的别名。erlang:halt()
,立即停止系统。
3. apply方法
内置函数apply(Mod, Func, [Arg1, Arg2, ..., ArgN])
等价于以下调用:
Mod:Func(Arg1, Arg2, ..., ArgN)
4. spawn方法
spawn
是Erlang的基本函数,用于创建一个并发进程并返回一个进程标识符。spawn语法:
spawn(Modname, FuncName, [Arg1, Arg2, ..., ArgN])
当Erlang系统执行spawn时,他会创建一个新进程(并非操作系统进程,而是一个由Erlang系统管理的轻量级进程;在Erlang中,模块相当于Java中的类,进程相当于Java中的对象)。当进程创建完毕之后,它便开始执行参数所指定的代码。Modname
是包含了所要执行代码的模块名。Funcname
是模块里的函数名。[…]是一个列表,包含了想要执行的函数参数。
5. 算术表达式
操作符 | 描述 | 参数类型 | 优先级 |
---|---|---|---|
+ X | + X | 数字 | 1 |
- X | - X | 数字 | 1 |
X * Y | X * Y | 数字 | 2 |
X / Y | X / Y(浮点除法) | 数字 | 2 |
bnot X | 对X执行按位取反(bitwise not) | 整数 | 2 |
X div Y | X被Y整除 | 整数 | 2 |
X rem Y | X除以Y的整数余数 | 整数 | 2 |
X band Y | 对X和Y执行按位与(bitwise and) | 整数 | 2 |
X + Y | X + Y | 数字 | 3 |
X - Y | X - Y | 数字 | 3 |
X bor Y | 对X和Y执行按位或(bitwise or) | 整数 | 3 |
X bxor Y | 对X和Y执行按位异或(bitwise xor) | 整数 | 3 |
X bsl N | 把X向左算术位移(arithmetic bitshift)N位 | 整数 | 3 |
X bsr N | 把X向右算术位移N位 | 整数 | 3 |
6. 比较数据类型
操作符 | 含义 |
---|---|
X > Y | X大于Y |
X < Y | X小于Y |
X =< Y | X等于或小于Y |
X >= Y | X大于或等于Y |
X == Y | X等于Y |
X /= Y | X不等于Y |
X =:= Y | X与Y完全相同 |
X =/= Y | X与Y不完全相同 |
数据类型排序:
number < atom < reference < fun < port < pid < tuple(record) < map < list < binary
所有的数据类型比较操作符(除了=:=
和=/=
)在参数全为数字时具有以下行为。
- 如果一个参数是整数而另一个是浮点数,那么整数会先转换成浮点数,然后再进行比较。
- 如果两个参数都是整数或者都是浮点数,就会“按原样”使用,也就是不做转换。
注意,==
只有在比较浮点数和整数时才有用。=:=
则是用来测试两个数据类型是否完全相同。
7. 下划线变量
_VarName
这种特殊语法代表一个常规变量(normalvariable),而不是匿名变量。一般来说,当某个变量在子句里只使用了一次时,编译器会生成一个警告,因为这通常是出错的信号。但如果这个只用了一次的变量以下划线开头,就不会有错误消息。
下划线变量有两种主要的用途:
- 命名一个我们不打算使用的变量。
- 用于调试。
8. 块表达式
begin
Exprssion1,
Exprssion2,
...,
ExprssionN
end
begin ... end
的值就是块里最后那个表达式的值。当代码某处的Erlang语法要求单个表达式,但我们想使用一个表达式
序列时可以使用。
9. 布尔表达式
not B1
:逻辑非
B1 and B2
:逻辑与
B1 or B2
:逻辑或
B1 xor B2
:逻辑异或
10. 短路布尔表达式
Expr1 orelse Expr2
它会首先执行Expr1
。如果Expr1
的执行结果是true
,Expr2
就不再执行。如果Expr1
的执行结果是false
,则会执行Expr2
。Expr1 andalso Expr2
它会首先执行Expr1
。如果Expr1
的执行结果是true
,则会执行Expr2
。如果Expr1
的执行结果是false
,Expr2
就不再执行。
11. ++和–
++
和--
是用于列表添加和移除的中缀操作符。
A ++ B
使A和B相加。
A -- B
从列表A中移除列表B。移除的意思是B中所有元素都会从A里面去除。请注意:如果符号X在B里只出现了K次,那么A只会移除前K个X。
1> A = [1,2,3,4,4,4].
[1,2,3,4,4,4]
2> B = [4,4,5].
[4,4,5]
3> A ++ B.
[1,2,3,4,4,4,4,4,5]
4> A -- B.
[1,2,3,4]
12. 宏
-define(MacroName, Value).
当Erlang的预处理器epp碰到一个?MacroName
形式的表达式时,就会展开这个宏,用Value
来代替。
预定义宏:
?FILE
展开成当前的文件名;
?MODULE
展开成当前的模块名;
?LINE
展开成当前的行号。
13. 宏控制流
-
-undef(MacroName).
取消宏的定义,此后就无法调用这个宏了。 -
-ifdef(MacroName).
仅当Macro有过定义时才执行后面的代码。 -
-ifndef(MacroName).
仅当Macro未被定义时才执行后面的代码。 -
-else
可用于ifdef
或ifndef
语句之后。如果条件为否,else
后面的语句就会被执行。 -
-endif
标记ifdef或ifndef语句的结尾。
可通过宏控制流来避免宏定义重复定义。例如,头文件如下:
-ifndef(FLAG).
-define(FLAG, 1).
-define(AAA, 111).
-define(BBB, 222).
-endif.
引入该头文件的时候,会先判断是否已经引入(通过判断FLAG
是否定义来实现),如果未定义那么就定义后续的宏定义,从而避免了头文件重复引用导致的宏定义重复定义。
14. 运行erl程序的不同方式
- 在 Erlang shell 里编译和运行
1> c(hello).
{ok,hello}
2> hello:start().
Hello EveryBody
ok
3>
- 在命令提示符界面里编译和运行
PS C:\Users\admin\Desktop> erlc hello.erl
PS C:\Users\admin\Desktop> erl -noshell -s hello start -s init stop
Hello EveryBody
PS C:\Users\admin\Desktop>
第1行的erlc hello.erl
编译了hello.erl
文件,生成了一个名为hello.beam
的目标代码文件。
第二个命令有三个选项。
-noshell
以不带交互式shell的方式启动Erlang(因此不会看到Erlang的“徽标”,也就是通常系统启动时首先显示的那些信息)。-s hello start
运行hello:start()
函数。注意:使用-s Mod ...
选项时,Mod
必须是已编译的。-s init stop
在之前的命令完成后执行init:stop()
函数,从而停止系统。
erl -noshell ...
命令可以放在shell脚本里,所以通常会制作一个shell脚本来运行程序,里面会设置路径(用-pa Directory
,指定寻找.beam
文件的目录)并启动程序。
命令行里的函数数量是不受限制的。每个-s ...
命令都由一个apply
语句执行,运行完毕后再执行下一个命令。
创建一个脚本hello.bat
:
erl -noshell -pa ../ebin -s hello start -s init stop
双击执行上述脚本:
C:\Users\admin\Desktop> erl -noshell -pa ebin -s hello start -s init stop
Hello EveryBody
- 用escript来让程序直接作为脚本运行,无需事先编译它们。
以下是一个在linux环境下的例子:
#!/usr/bin/env escript
main(Args) ->
io:format("Hello EveryBody~n", []).
将文件设置成可执行文件后直接运行即可。
- 用EMakeFile使编译自动化
通过make
命令对erl文件进行编译,make
命令会在指定目录或当前目录寻找EMakeFile
文件,EMakeFile
内指定了要编译的模块集和要使用的编译选项。
15. 故障分析
如果Erlang崩溃了,它会留下一个名为erl_crash.dump
的文件。这个文件的内容也许能提示你问题出在哪里。有一个基于Web的故障分析器可以用来分析故障转储文件。要启动这个分析器,请输入以下命令:
crashdump_viewer:start().
然后把浏览器指向http://localhost:8888/
,这样就可以愉快地浏览错误日志了。
注:本博客为通过《Erlang程序设计 第二版》学习Erlang时所做的笔记。学习更详细的内容,建议直接阅读《Erlang程序设计 第二版》。