Erlang笔记 -- 基础

本文档是关于Erlang编程的基础笔记,涵盖了Erlang的Hello World、Erlang Shell、基本数据类型如数字、变量、原子、元组、列表和字符串,以及模块、fun、条件句式等内容。讲解了Erlang的注释、变量绑定、模块属性、函数导出、编译和运行Erlang程序的不同方法,还包括Erlang中的条件表达式如case和if,以及Erlang Shell的常用命令。此外,还介绍了Erlang的宏、模块属性、算术和逻辑运算以及错误分析等核心概念。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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里的数字不是整数就是浮点数。

  1. 整数
    整数的运算是精确的,而且用来表示整数的位数只受限于可用的内存。
    整数可以有三种不同的写法。
  • 传统写法
  • K进制整数 除10以外的数字进制整数使用K#Digits这种写法。
1> 16#11.
17
2> 32#12.
34
  • $写法 $C这种写法代表了ASCII字符C的整数代码。
  1. 浮点数
    一个浮点数由五部分组成:一个可选的正负号,一个整数部分,一个小数点,一个分数部分和一个可选的指数部分。
1> 4/2.
2.0
2> 5/3.
1.6666666666666667
3. 变量
  1. Erlang中的变量以大写字母开头。
  2. 可用"="操作符给变量赋值,严格来说是给变量绑定一个值。因为在Erlang中,变量一旦绑定了值,就不允许重新绑定。“=” -> 模式匹配符。
  3. 以小写字母开头的不是变量而是符号常量,称为原子。
4. 原子(atom)
  1. 在Erlang中,原子是全局性的,而且是不需要宏定义和包含文件即可实现的。
  2. 原子以小写字母开头,后接一串字母、数字、下划线、@符号。原子还可以放在单引号( ’ )中。可以用这种方式创建以大写字母开头的或者包含其他符号的原子,如'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(...),它们被用来定义文件的某些属性。有两种类型:预定义型+用户定义型。

  1. 预定义型:
  • -module(modname).
    这是模块声明。modname必须是一个原子。此属性必须是文件里的第一个属性。按照惯例,modname的代码应当保存在名为modname.erl的文件里。如果不这么做,自动代码加载就不能正常工作。
  • -import(Mod, [FuncName1/ArityNum1, FuncName2/ArityNum2]).
    import声明列举了哪些函数需要导入到模块中。上面这个声明的意思是要从Mod模块导入参数数量为ArityNum1FuncName1函数,参数数量为ArityNum2FuncName2函数。
    一旦从别的模块里导入了某个函数,调用它的时候就无需指定模块名了。
  • -export([FuncName1/ArityNum1, FuncName2/ArityNum2]).
    导出当前模块里的FuncName1/ArityNum1FuncName2/ArityNum2函数。函数只有被导出后才能在模块之外调用。
  • -compile(Options).
    -compile(export_all).这个编译器选项经常会在调试程序时用到。它会导出模块里的所有函数,无需再显式使用-export标识了。
  • -vsn(Version).
    指定模块的版本号。Version可以是任何字面数据类型。Version的值没有什么特别的语法或含义,但可以用于分析程序或者作为说明文档使用。
  1. 用户定义型:
    语法: -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可以是exportsimportsattributescompile中的一个)会返回与模块相关的单个属性。

fun:基本的抽象单元

Erlang是一种函数式编程语言。函数式编程语言表示函数可以被用作其他函数的参数,也可以返回函数。操作其它函数的函数称为高阶函数,而在Erlang中用于代表函数的数据类型称为funfun是“匿名的”函数。

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想得到更多的输入。

  1. 以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的元素,即对于元素EP(E)true

filtermap等函数能够在一次调用里对整个列表执行某种操作,称为“一次一列表式操作”

lists:member(X, L),当X是是列表L中的成员,返回true,否则返回false

  1. 返回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>

括号里的funfun(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被执行,得到值ValueValue依次与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,以此类推,直至某个Guardtrue时。
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的方法
  1. CTRL+C,会立即停止系统,可能导致数据损坏
  2. q(),受控关闭,所有打开的文件都会被刷入缓存并关闭,数据库(正在运行的)会被停止,所有应用程序都会以有序的方式关停。q()init:stop()命令在shell里的别名。
  3. 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 * YX * Y数字2
X / YX / Y(浮点除法)数字2
bnot X对X执行按位取反(bitwise not)整数2
X div YX被Y整除整数2
X rem YX除以Y的整数余数整数2
X band Y对X和Y执行按位与(bitwise and)整数2
X + YX + Y数字3
X - YX - 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 > YX大于Y
X < YX小于Y
X =< YX等于或小于Y
X >= YX大于或等于Y
X == YX等于Y
X /= YX不等于Y
X =:= YX与Y完全相同
X =/= YX与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的执行结果是trueExpr2就不再执行。如果Expr1的执行结果是false,则会执行Expr2
  • Expr1 andalso Expr2
    它会首先执行Expr1。如果Expr1的执行结果是true,则会执行Expr2。如果Expr1的执行结果是falseExpr2就不再执行。
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
    可用于ifdefifndef语句之后。如果条件为否,else后面的语句就会被执行。

  • -endif
    标记ifdef或ifndef语句的结尾。

可通过宏控制流来避免宏定义重复定义。例如,头文件如下:

-ifndef(FLAG).
-define(FLAG, 1).

-define(AAA, 111).
-define(BBB, 222).
   
-endif.

引入该头文件的时候,会先判断是否已经引入(通过判断FLAG是否定义来实现),如果未定义那么就定义后续的宏定义,从而避免了头文件重复引用导致的宏定义重复定义。

14. 运行erl程序的不同方式
  1. 在 Erlang shell 里编译和运行
1> c(hello).
{ok,hello}
2> hello:start().
Hello EveryBody
ok
3>
  1. 在命令提示符界面里编译和运行
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
  1. 用escript来让程序直接作为脚本运行,无需事先编译它们。
    以下是一个在linux环境下的例子:
#!/usr/bin/env escript
main(Args) ->
    io:format("Hello EveryBody~n", []).

将文件设置成可执行文件后直接运行即可。

  1. 用EMakeFile使编译自动化
    通过make命令对erl文件进行编译,make命令会在指定目录或当前目录寻找EMakeFile文件,EMakeFile内指定了要编译的模块集和要使用的编译选项。
15. 故障分析

如果Erlang崩溃了,它会留下一个名为erl_crash.dump的文件。这个文件的内容也许能提示你问题出在哪里。有一个基于Web的故障分析器可以用来分析故障转储文件。要启动这个分析器,请输入以下命令:

crashdump_viewer:start().

然后把浏览器指向http://localhost:8888/,这样就可以愉快地浏览错误日志了。

注:本博客为通过《Erlang程序设计 第二版》学习Erlang时所做的笔记。学习更详细的内容,建议直接阅读《Erlang程序设计 第二版》。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值