文章目录
一、Erlang Shell
-
启动 Erlang Shell
erl
参数 说明 -noshell 不显示 Erlang Shell,执行任务时使用 windows系统
erl %% 可以使用重定向标准输入输出 或 管道 werl %% 支持编辑快捷键,显示正确的编码字符
-
全局函数/命令
命令 说明 help(). 打印可用的shell函数 h(). 打印先前输入过的命令 v(N). 取出第N号提示符对应的计算结果 f() 让 shell 忘记现有的任何绑定 cd(Dir). 更改当前目录(Dir是双引号字符串) ls(). 打印目录内容 pwd(). 打印当前目录路径 i(). 打印当前系统的运行时信息 memory(). 打印内存使用信息 q().
init:stop().有序的方式关停,并退出 Erlang Shel halt().
erlang:halt().立即退出 Erlang Shel Erlang Shell 里不能输入附注("-"开头的语句)
-
快捷键
命令 说明 Ctrl + C 调出BREAK菜单 Ctrl + G 调出用户开关命令菜单,
输入h 显示可用命令
回复控制:i 回车 c 回车Ctrl + A 行首 Ctrl + D 删除当前字符 Ctrl + E 行尾 Ctrl + F 或→ 向前的字符 Ctrl + B 或← 向后的字符 Ctrl + P 或 ↑ 前一行 Ctrl + N 或 ↓ 下一行 Ctrl + T 调换最近两个字符的位置 Tab 尝试扩展当前模块或函数的名称
二、操作符
操作符 | 说明 |
---|---|
% | 行注释 |
() | 优先运算符 |
= | 赋值, 匹配 |
, | 元素分隔符 |
; | 句子分隔符 |
. | 语句结束符 |
算数操作符
操作符 | 描述 | 参数类型 | 优先级 |
---|---|---|---|
+ X | 正 | 数字 | 1 |
- X | 负 | 数字 | 1 |
X * Y | 乘 | 数字 | 2 |
X / Y | 浮点除 | 数字 | 2 |
bnot X | 按位取反 | 整数 | 2 |
X div Y | 整除 | 整数 | 2 |
X rem Y | 取余 | 整数 | 2 |
X band Y | 按位与 | 整数 | 2 |
X + Y | 加 | 数字 | 3 |
X - Y | 减 | 数字 | 3 |
X bor Y | 按位或 | 整数 | 3 |
X bxor Y | 按位异或 | 整数 | 3 |
X bsl N | 把 X 向左算术位移 N 位 | 整数 | 3 |
X bsr N | 把 X 向右算术位移 N 位 | 整数 | 3 |
逻辑操作符
操作符 | 说明 |
---|---|
not B1 | 逻辑非 |
B1 and B2 | 逻辑与 |
B1 andalso B2 | 短路逻辑与 |
B1 or B2 | 逻辑或 |
B1 orelse B2 | 短路逻辑或 |
B1 xor B2 | 逻辑异或 |
B1和B2都必须是布尔值或者执行结果为布尔值的表达式
- andalso 与 and 的区别
% 当 X 为 0 时,f(X) 里的关卡会失败,但 g(X) 里的关卡会成功 f(X) when (X == 0) or (1/X > 2) -> ... g(X) when (X == 0) orelse (1/X > 2) -> ...
比较操作符
允许不同变量进行比较操作
操作符 | 说明 |
---|---|
< =< | 小于 小于等于 |
> >= | 大于 大于等于 |
=:= == | 等于(区分整型和浮点型) 等于(不区分数字类型) |
=/= /= | 不等于(区分整型和浮点型) 不等于(不区分数字类型) |
数据类型之间的大小顺序:
number < atom < reference < fun < port < pid < tuple < list < bit string
三、变量
- 变量的首字母大写,变量的值不可改变
- 定义变量
1> One. %% 使用未绑定的变量,报错 * 1: variable 'One' is unbound 2> One = 1. %% 变量 One 的值为 1 1 3> Un = Uno = One = 1. %% 变量Un、Uno、One 的值为 1 1 4> Two = One + One. %% 变量 Two 的值为 2 2 5> Two = 2. %% 变量 Two 的值为 2 2 6> Two = Two + 1. %% 变量 Two 的值 为 3,报错,值不能改变 ** exception error: no match of right hand side value 3
模式匹配
用模式匹配来代替赋值操作
模式 = 单元 | 结果 |
---|---|
_ | 能重复匹配任何数据 |
{X, abc} = {123, abc}. | 成功:X = 123 |
{X, Y, Z} = {222, def, “cat”}. | 成功:X = 222, Y = def, Z = “cat” |
{X, Y} = {333, ghi, “cat”}. | 失败:元组的结构不同 |
X = true. | 成功:X = true |
{X, Y, X} = {{abc, 12}, 42, {abc, 12}}. | 成功:X = {abc, 12} Y = 42 |
{X, Y, X} = {{abc, 12}, 42, true}. | 失败:X的值不同({abc, 12} 与 true 不同) |
[H | T] = [1, 2, 3, 4, 5]. | 成功:H = 1, T = [2, 3, 4, 5] |
[H | T] = “cat”. | 成功:H = 99, T = “at”] |
[A, B, C | T] = [a, b, c, d, e, f] | 成功:A = a, B = b, C = c, T = [d, e, f] |
四、数据类型
基础数据类型:
类型 | 说明 | 备注 |
---|---|---|
Integer | 整型 | |
Float | 浮点型 | |
Atom | 原子型 | |
bool | 布尔型 | |
Tuple | 元组型 | |
List | 列表 | |
String | 字符串 | |
fun | 函数型 | |
Union | ||
binary | 二进制型 | |
bitstring | 二进制串 | |
record | 记录 | |
map | 映射组 |
内部类型:
类型 | 说明 | 备注 |
---|---|---|
any() | ||
none() | ||
pid() | 进程id | |
port() | 端口 | |
reference() | ||
[] | ||
Atom | ||
binary() | ||
float() | ||
Fun | ||
Integer | ||
Tuple | ||
Union | ||
UserDefined | ||
ref |
整型 integer
- 定义整数
1> N10 = 15. %% 十进制
- 进制整数,用 base#value 表示2 ~ 36进制数,超出9的数用 abc…z 等字符代替。
1> N2 = 2#00101010. %% 二进制 2> N8 = 8#123756322. %% 八进制 3> N16 = 16#af6bfa23. %% 十六进制
- 用 ASCII 编码表示整数或字符,格式:$char ???
1> $a. %% 字符 a 的ASCII 编码是 97 2> $1. %% 字符 1 的ASCII 编码是 49 3> $\n. %% 换行符 的ASCII 编码是 10 4> $\^c. %% Ctrl+C 的ASCII 编码是 3
浮点型 float
采用 IEEE 754-1985 格式的 64 位双精度浮点数。可以表示绝对值在 10-323 到 10308 范围内的实数。由五部分组成:
- 可选的正负号
- 整数部分
- 小数点
- 小数部分
- 可选的指数部分
- 定义浮点数
1> F1 = 1.0. 2> F2 = 3.1415926. 3> F3 = -2.3e+6. 4> F4 = 23.56E-27.
原子 atom
- 原子被用于表示常量值,是全局性的。
- 原子以小写字母开头,后接一串字母、数字、下划线 (_) 或at (@) 符号
- 用单引号 ’ 包裹原子名,用来创建除字母和数字以外字符的原子(含首字母大写、特殊符号、句子等)
- 原子值就是它本身,cat 与 ‘cat’ 等价
- 原子在32位系统中占4个字节,在64位系统中占8个字节。
- 原子都会被引用到一个原子表中,该表不会被垃圾回收。当内存耗尽或原子个数超过 1 048 577时,系统会崩溃。所以不要动态创建原子,
- 定义原子
1> cat. cat 2> 'Cat'. 'Cat' 3> cat =:= 'cat'. true
布尔型 bool
Erlang 中没有布尔型,true 和 false 都是原子
元组 tuple
元组会在定义时自动创建,不再使用时则被销毁
- 定义元组
1> P1 = {10, 45}. %% 声明元组 2> P2 = {point, 10, 45}. %% 增加可读性 3> P3 = {person, {name, joe}, {height, 1.82}}. %% 元组嵌套
- 提取元组的值
1> {X, Y} = {10, 45}. %% X为10,Y为45 2> {point, X, Y} = {point, 10, 45}. %% X为10,Y为45 3> {point, C, C} = {point, 25, 25}. %% C为25,如值不同则报错 4> {_, {_, Who}, _} = P3. %% Who为joe。_为通配符
列表 list
列表的第一个元素叫表头(head),其余的叫表尾(tail)。注意列表头可以是任何事物,但列表尾通常仍然是个列表。
举例:T是个列表,[H|T]也是个列表,头是H,尾是T。竖线(|)把头与尾隔开。[]是个空列表。
访问列表头是一种非常高效的操作,先处理表头,再处理表尾。
-
定义列表
1> Drawing = [{square,{10,10},10}, {triang,{15,10},{25,10},{30,40}}]. 2> ThingsToBuy = [{apple,10}, {pears,6}, {milk,3}]. %% 多表头 3> ThingsToBuy1 = [{oranges,4}, {newspaper,1} | ThingsToBuy].
-
提取列表的元素
1> [Buy1 | ThingsToBuy2] = ThingsToBuy1. %% Buy1 = {oranges, 4} %% ThingsToBuy2 = [{newspaper,1}, {apples,10}, {pears,6}, {milk,3}] %% 多表头 2> [Buy2, Buy3 | ThingsToBuy3] = ThingsToBuy2. %% Buy2 = {newspaper,1} %% Buy3 = {apples,10} %% ThingsToBuy3 = [{pears,6}, {milk,3}]
如果有一个非空列表 L,那么表达式 [X | Y] = L (X, Y都是未绑定变量)会提取列表头作为X,列表尾作为Y。
-
列表元素操作
++ 可以把列表粘合起来,-- 可以删除列表中的元素。1> [1, 2, 3] ++ [4, 5]. [1, 2, 3, 4, 5] 2> [1, 2, 3, 4, 5] -- [1, 2, 3]. [4, 5] 3> [2, 4, 2] -- [2, 4]. [2]
两个操作符都是右结合的。所以都要从右向左进行操作。
1> [1, 2, 3] -- [1, 2] -- [3]. [3] 2> [1, 2, 3] -- [1, 2] -- [2]. [2, 3]
数据量大时不推荐使用,效率低
列表推导 list comprehension
列表推导(list comprehension)是无需使用fun、map或filter就能创建列表的表达式。它让 程序变得更短,更容易理解。
构造自然顺序的列表
- 总是向列表头添加元素。
- 从输入列表的头部提取元素,然后添加到输出列表的头部,形成的结果与输入列表顺序相反的输出列表。
- 如果顺序很重要,调用lists:reverse/1函数(高度优化过)
- 避免违反以上的建议。
-
普通形式 [ expression || Pattern <- ListExpr ]
英文 中文 说明 expression 表达式 用于生成新的元素 Pattern 模式 用于匹配 list 中的各个元素 ListExpr 列表 用生成新列表的源列表 示例:
1> [ 2*X || X <- [1,2,3,4,5] ]. [2,4,6,8,10] 2> map(F,L) -> [F(X) || X <- L]. %% 等价于 map(F, [H | T]) -> [F(H) | map(F, T)].
从 [1,2,3,4,5] 中按顺序挨个取出一个元素匹配给 X,然后用 2*X 计算出新列表相应顺序的元素值。[2,4,6,8,10]
-
常见形式 [ X || Qualifier1, Qualifier2, …]
英文 中文 说明 X 表达式 任意一条表达式 Qualifier 限定符 可以是 generator 或 bitstring、和 filter 带过滤器的示例:
1> [X || X <- [1,2,3,4], X rem 2 =:= 0]. [2, 4]
Qualifier
英文 中文 形式 说明 generator 生成器 Pattern <- ListExpr ListExpr:必须是能得出列表的表达式 bitstring 位串生成器 BitStringPattern <= BitStringExpr BitStringExpr 必须是能得出 bitstring 的表达式 filter 过滤器 既可以是判断函数(返回true或false), 也可以是布尔表达式 列表推导里的生成器部分起着过滤器的作用 多个生成器的示例:
1> [X+Y || X <- [1, 2], Y <- [3, 4]]. [4, 5, 5, 6] %% 会执行 1+3、1+4、2+3、2+4 的操作
生成器启动过滤的作用的示例:
1> [X || {a, X}<-[{a,1}, {b,2}, {c,3}, {a,4}, hello, "wow"]]. [1,4]
二进制型 binary
二进制型节省空间,用来保存大量的原始数据(大型字符串、文件内容)
- 总位数是 8 的倍数。
- 定义二进制型:
<<E1, E2, ..., En>>
1> B1 = <<5, 10, 20>>. %% 整数(0~255) <<5,10,20>> 2> B2 = <<"hello">>. %% 字符串 <<"hello">> 3> B3 = <<65, $B, 67>>. %% 内容是的 ASCII 码 <<"ABC">>
位语法
用来定义二进制型和位串
-
格式:
<<E1, E2, ..., En>>
Ei为其中某一元素。
Ei = Value |
Value:Size |
Value/TypeSpecifierList |
Value:Size/TypeSpecifierList -
Value 值
范围 类型 示例 定义时 字符串
整数(0~255)
浮点数
二进制型
已绑定变量
表达式<<“example”>>
<<11, 22, 33>>
<<>>
<<>>
A = 12. <<A, A>>.
<<>>模式匹配 未绑定变量
字符串
整数(0~255)
浮点数
二进制型
已绑定变量<<X1, X2>> = <<-44>>
<<>>
<<>>
<<>>
<<>>
<<>> -
Size 所占位数
范围 类型 示例 定义时 整数
表达式模式匹配 整数
已绑定变量Size 的默认值会根据 Value 的类型变化。在模式匹配里,默认值只对最后那个元素有效。
类型 默认值 整数 8 浮点数 64 二进制型 是其大小 -
TypeSpecifierList 类型指定列表
形式:End-Sign-Type-Unit;顺序任意,都可省略(为默认值)形式 描述 说明 End 字节序列 big:高位优先,网络字节顺序(network byte order)。默认值
little:低位优先
native:在运行时根据机器的 CPU 来确定Sign 符号类型 unsigned 无符号,默认值
signed:有符号
只用于模式匹配Type 数据类型 integer,默认值
float
binary | bytes
bitstring | bits
utf8
utf16
utf32Unit 数据单位 写法:unit:1|2|…256
integer、float、bitstring 的默认值是1
binary 的默认值为 8
utf8、utf16、utf32 类型无需定义这个值的所占空间 = Size * Unit
待整理的示例:
%% 存储24位真彩色
1> Color = 16#F09A29.
15768105
2> pixel = <<Color:24>>.
<<240, 154, 41>>
3> Pixels = <<213, 45, 132, 64, 76, 32, 76, 0, 0, 234, 32, 15>>.
<<213,45,132,64,76,32,76,0,0,234,32,15>>
4> <<Pix1:24, Pix2:24, Pix3:24, Pix4:24>> = Pixels.
<<213,45,132,64,76,32,76,0,0,234,32,15>>
5> <<R:8, G:8, B:8>> = <<Pix1:24>>.
<<213,45,132>>
6> R.
213
7> <<R1:8, Rest/binary>> = Pixels.
<<213,45,132,64,76,32,76,0,0,234,32,15>>
8> R1.
213
%% 获取 Size
<<Size:4, Data:Size/binary>> = <<1,2,3,4,5,6>>
M = <<X:3, Y:7, Z:6>> %M的类型是binary 因为数据的总长度是16位,可以被8整除
M = <<X:2, Y:7, Z:6>> %M的类型是bitstring 因为总位数是15
1> Red = 2.
2> Green = 61.
3> Blue = 20.
% 打包颜色, 定义一个16位数据的双字节二进制型
4> Mem = <<Red:5, Green:6, Blue:5>>.
<<23,180>>
% 解包颜色
5> <<R1:5, G1:6, B1:5>> = Mem.
位串:bitstring
位串(bitstring)的模式匹配是位级操作,这样我们就能在单次操作里打包和解包 位的序列。
这对编写需要操作位级数据的代码来说极其有用,例如没有按照8位边界对齐的数据或者可变长度数据,它们的数据长度用位而不是字节来表示。
1> B1 = <<1:8>>.
<<1>>
2> byte_size(B1).
1
3> is_binary(B1).
true
4> is_bitstring(B1).
true
5> B2 = <<1: 17>>.
<<0,0,1:1>>
6> is_binary(B2).
false
7> is_bitstring(B2).
true
8> byte_size(B2).
3 %% 字节大小
9> bit_size(B2).
17 %% 位大小
因为文件和套接字使用的单位是字节,所以不能把一个位串写入文件或套接字。
位推导 bit comprehension
提取出字节里各个单独的位,
遍历二进制型并生成列表或二进制型。
1> B = <<16#5f>>.
<<"_">> %% 包含单个字节的二进制型
2> [X || <<X:1>> <= B].
[0,1,0,1,1,1,1,1] %% 包含字节里各个位的列表
3> << <<X>> || <<X:1>> <= B >>.
<<0,1,0,1,1,1,1,1>> %% 包含这些位的二进制型
字符串 string
Erlang 用列表和二进制型表示字符串。
-
定义字符串
1> Name = "Kate". "Kate"
在 shell 中,如果列表内的所有整数都代表可打印字符(ASCII 码),那么就会将其打印成字符串字面量,否则,打印成列表。
2> [83, 117, 114, 112, 114, 105, 115, 101]. "Surprise" 3> [1, 83, 117, 114, 112, 114, 105, 115, 101]. [1,83,117,114,112,114,105,115,101]
-
用 $char 格式输出字符的 ASCII 码
1> $a. 97 2> $1. 49 3> $\n. 10 4> $\^c. 3 5> I = $s. 115 6> [I-32, $u, $r, $p, $r, $i, $s, $e]. "Surprise"
-
输出数字或 Unicode 字符
1> X = [97,98,99]. "abc" 2> io:format("~w~n",["abc"]). [97,98,99] %% 输出 Unicode字符 3> X1 = "a\x{221e}b". [97,8734,98] 4> io:format("~ts~n", [X1]). a∞b
-
字符集
从 Erlang R16B 开始,Erlang 的源代码文件都假定采用UTF-8 字符集编码。在这之前用的是 ISO-8859-1(Latin-1)字符集。这意味着所有 UTF-8 可打印字符都能在源代码文件里使用,无需使用任何转义。Erlang内部没有字符数据类型。字符串其实并不存在,而是由整数列表来表示。用整数列表表示 Unicode 字符串是毫无问题的。
-
转义字符
转义字符 含义 整数编码 \b 退格符 8 \d 删除符 127 \e 换行符 27 \f 换页符 12 \n 换行符 10 \r 回车符 13 \s 空格符 32 \t 水平制表符 9 \v 垂直制表符 11 \x{…} 十六进制字符 ^a…^z
或 ^A…^ZCtrl+A 至 Ctrl+Z 1 至 36 \’ 单引号 39 \" 双引号 34 \\ 反斜杠 92 \C C的 ASCII编码(C是一个字符) (一个整数) -
各种字符串的区别
表现方式 占用空间 匹配/操作 比较 列表字符串 多 简单 线性时间 二进制字符串 少 复杂 线性时间 原子 有上限 只能比较 常量时间(快)
五、模块 module
- 模块是 Erlang 的基本代码单元。模块文件的扩展名为 .erl
- 必须先编译才能运行模块里的代码;编译后的模块以 .beam 作为扩展名
- 模块中定义函数和属性。
- 属性是元数据,用来描述模块自身。定义属性用
-Name(Attribute).
的形式。
-module(Name).
模块名:必须定义的第一条属性,其名称必须与文件名相同,首字母小写,是一个原子(atom)。
-author(Name).
模块的作者。
-vsn(VersionNumber).
模块版本号,默认自动生成 - 语句都必须在方法内才能被执行。
- 模块不能环形依赖(A引用B,B又引用A)
-
创建模块文件 geometry.erl
-module(geometry). %% 定义模块 -export([area/1]). %% 导出方法 -import(other, [fn1/1,fn2/2]). %% 导入模块 other 的方法 fn1 和 fn2; %% /n 表示有n个参数 %% 定义方法:计算长方形的面积 %% 首先该方法进行匹配:使得 Width=10、Height=5 %% 然后再执行 -> 后的代码 Width * Height; 计算得 50 area({rectangle, Width, Height}) -> Width * Height; %% 方法重载,用分号分隔 area({circle, Radius}) -> 3.14159 * Radius * Radius; %% 结束用句号结尾 area({square, Side}) -> Side * Side.
第一行
-module(geometry).
为声明 geometry 模块。第二行
-export([area/1]).
为导出方法,area 为方法名,/1 表示该方法有一个参数。第三行
-import(other, [fn1/1,fn2/2]).
为导入 other 模块中的方法 fn1/1 和 fn2/2。 -
在 Erlang Shell 中编译并执行模块代码
用内置函数c/1
对 geometry.erl 进行编译。并生成 geometry.beam 文件%% 1. 编译模块 1> c(geometry). {ok,geometry} %% 2. 带调试信息的编译模块 %% debug_info 开启调试信息,建议一直开启 %% export_all 导出模块内所有方法 2> c(geometry, [debug_info, export_all]). %% 2.1 也可以在模块内添加属性达到同样的效果 %% -compile([debug_info, export_all]).
不进入 Erlang Shell,直接编译模块
erlc geometry.erl
编译成本地代码(需平台的支持),传闻快20%,但是 .beam 文件不能跨平台使用。
hipe:c(Module, OptionsList). # 或在 Erlang Shell 中执行: 1> c(Module, [native]).
-
调用模块中的方法:
Module:Function(Arguments)
2> geometry:area({rectangle, 10, 5}). 50
-
测试代码(生产环境还是用功能全的测试代码)
-module(geometry). -export([area/1]). test() -> 12 = area({rectangle, 3, 4}), 144 = area({square, 12}), tests_wored. area({rectangle, Width, Height}) -> Width * Height; area({square, Side}) -> Side * Side.
宏
- 在模块里定义宏:
-define(MACRO, some_value).
%% 常量宏 -define(HOUR, 3600). %单位是秒 %% 函数宏 -define(sub(X,Y), X-Y). %两个数相减
- 在编译时定义宏
c(MODULE, [{d, Macro, Value},...])
Value 可以省略1> c(main, [{d, 'DEBUGMODE'}]).
- 使用宏:
?MACRO
%% Erlang 内置的宏 ?MODULE % 模块名 ?FILE % 文件名 ?LINE % 当前行号
- 判断宏:
-ifdef(MACRO).
、-else.
、endif.
-ifdef(DEBUGMODE). -define(DEBUG(S), io:format("dbg:"++S)). -else. -define(DEBUG(S), ok). -endif
元数据
模块属性是描述模块自身的元数据。
- 查看模块的元数据
Module:module_info().
%% 编译模块 1> c(main). %% 查看模块 main 的信息。 2> main:moudle_info(). [{module,main}, {exports,[{run,3}, {sum,1}, {map,2}, {module_info,0}, {module_info,1}]}, {attributes,[{vsn,[215390944566671778967536641503511308425]}]}, {compile,[{version,"7.5.4"}, {options,[native]}, {source,"/Users/lixu/example/erl_demo/main.erl"}]}, {native,true}, {md5,<<119,213,217,218,169,99,126,49,30,250,134,13,165, 26,146,174>>}]
- 查看模块中的指定信息
Module:module_info/1
.3> mian:module_info(attributes). [{vsn,[215390944566671778967536641503511308425]}]
头文件
- 扩展名为 .hrl 的文件是同名模块的头文件
六、函数 fun
-
定义函数
在 模块 里定义函数hypot(X, Y) -> %% 定义 命名函数 math:sqrt(X*X + Y*Y). %% 最后一句是函数的返回值
在 模块 和 shell 里定义匿名函数
1> Double = fun(X) -> 2*X end. %% 定义 匿名函数 2> Double(5). %% 调用 匿名函数
函数中的语句用逗号 , 分隔,用 . 结尾
-
函数重载
在 Erlang Shell 中定义重载函数(参数个数相同,值不同)%% 转换华氏度与摄氏度 1> TempConvert = fun({c, C}) -> {f, 32+ C*9/5}; 1> ({f, F}) -> {c, (F-32)*5/9} %% 省略了fun 1> end. #Fun<erl_eval.7.126501267> 2> TempConvert({c, 100}). {f,212.0} 3> TempConvert({f, 212}). {c,100.0}
在 模块 中定义重载函数
%% 导出模块内的方法(函数) -export([abc/1, abc/2]). abc({sum, A, B}) -> A * B. abc({total, A, B}) -> A + B. %% 参数个数相同 abc({sum, A, B}, C) -> A + B + C. %% 参数个数不同
定义模块的方法:用分号 ; 间隔同名函数的不同实现。最后以 . 结尾。
函数中没有显式的返回语句,用最后一句表达式的值当返回值。
模式匹配
- 函数的参数使用模式匹配来重载函数
%% 根据性别区分敬语 greet(male, Name) -> io:format("Hello, Mr. ~s", [Name]); greet(female, Name) -> io:format("Hello, Mrs. ~s", [Name]); greet(_, Name) -> io:format("Hello, ~s!", [Name]).
- 函数的参数可以使用 = 来匹配元组的内部元素同时,还匹配整个元组。
valid_time({ Date = { Y, M, D }, Time = { H, Min, S} }) -> io:format("The Date tuple (~p) says today is: ~p/~p/~p, ~n", [Date, Y, M, D]), io:format("The Time tuple (~p) indicates: ~p:~p:~p. ~n", [Time, H, Min, S]); valid_time(_) -> io:format("Stop feeding me wrong data!~n").
- 模式匹配的局限性
a.可能匹配到结构相同,但类型错误的值。
b.不确定参数的取值范围。
这时候就需要 关卡 来解决来。
关卡 guard
关卡是模式匹配的一种扩展。
可以对某个模式里的变量执行简单的测试和比较。
-
示例:求两个数的最大值
is_positive(X) when is_integer(X), X > 0 -> true; is_positive(_) -> false.
当 X 为整型时,并且X 大于 0 时句 1 匹配,返回 true。
当句1不匹配时,句2匹配,返回 false。 -
使用在函数的头部使用关卡(由 when 引入);或在支持表达式的任何地方使用。
-
当关卡被用作表达式时,执行的结果是 true 或 false。
-
关卡由一系列关卡表达式组成,用逗号 “,” 分割。只有GuardExpr1, GuardExpr2, …,GuardExprN 等所有的关卡表达式都为 true 时才为 true
-
合法的关卡表达式是所有合法Erlang表达式的一个子集。关卡不能调用用户定义的函数,来确保关卡表达式的执行是无副作用的。
-
下列语法形式是合法的关卡表达式:
合法的关卡表达式 说明 原子 true 其它常量(各种数据结构和已绑定变量),在关卡表达式里都为 false 调用后面表1里的关卡判断函数和表2里的内置函数 数据结构比较 参考表6 算术表达式 参考表3 布尔表达式 参考8.7 短路布尔表达式 参考8.23 -
关卡序列是指单一或一系列的关卡,用分号“;”分隔。对于关卡序列G1; G2; …; Gn,只要其中一个关卡的值为 true,它的值就为 true。
-
关卡示例:
%% 判断两个值的大小 max(X, Y) when X > Y -> X; %% 当 X 大于 Y 时,返回 X max(_, Y) -> Y. %% 如 X 不大于 Y,则返回 Y %% 当X是一个整数、并且 X 大于 Y,并且 Y 小于 6 时匹配 f(X,Y) when is_integer(X), X > Y, Y < 6 -> ... %% T是一个包含六个元素的元组,并且T中第三个元素的绝对值大于5 is_tuple(T), tuple_size(T) =:= 6, abs(element(3, T)) > 5 %% 元组 X 的第 4 个元素与列表 L 的列表头相同 element(4, X) =:= hd(L) %% (X 等于 cat) 或者 (X 等于 dog) X =:= dog; X =:= cat %% (X 是整数,并且 X 大于 Y) 或者 Y 的绝对值小于 23 is_integer(X), X > Y; abs(Y) < 23 %% A 大于等于 -1 并且 A 加 1 小于 B A >= -1.0 andalso A+1 > B %% 如果 L 是个原子,那么判断 L 是个列表 并且 L 的长度大于 2 is_atom(L) orelse ( is_list(L) andalso length(L) > 2)
表1 返回关卡
判断函数 | 说明 |
---|---|
is_atom(X) | X是一个原子 |
is_binary(X) | X是一个二进制型 |
is_constant(X) | X是一个常量 |
is_float(X) | X是一个浮点数 |
is_function(X) | X是一个fun |
is_function(X, N) | X是一个带有N个参数的fun |
is_integer(X) | X是一个整数 |
is_list(X) | X是一个列表 |
is_map(X) | X是一个映射组 |
is_number(X) | X是一个整数或浮点数 |
is_pid(X) | X是一个进程标识符 |
is_pmod(X) | X是一个参数化模块的实例 |
is_port(X) | X是一个端口 |
is_reference(X) | X是一个引用 |
is_tuple(X) | X是一个元组 |
is_record(X,Tag) | X是一个类型为Tag的记录 |
is_record(X,Tag,N) | X是一个类型为Tag、大小为N的记录 |
表2 返回关卡
内置函数 | 说明 |
---|---|
abs(X) | X的绝对值 |
byte_size(X) | X的字节数,X必须是一个位串或二进制型 |
element(N, X) | X里的元素N,注意:X必须是一个元组 |
float(X) | 将X 转换成一个浮点数,X必须是一个数字 |
hd(X) | 列表 X 的列表头 |
length(X) | 列表X的长度 |
node() | 当前的节点 |
node(X) | 创建X的节点,X可以是一个进程、标识符、引用或端口 round(X) |
self() | 当前进程的进程标识符 |
size(X) | X的大小,它可以是一个元组或二进制型 |
trunc(X) | 将X去掉小数部分取整,X必须是一个数字 |
tl(X) | 列表X的列表尾 |
tuple_size(T) | 元组T的大小 |
if 表达式
-
if 表达式的语法:
if Guard1 -> Expr_seq1; Guard2 -> Expr_seq1; ... GuardN -> Expr_seqN end, %% 多个 if 用逗号分隔 if ... end. %% 用英文句号结束
如果 Guard1 的值为 true,那么执行 Expr_seq1,if 的值 为 Expr_seq1 的值
如果 Guard1 的值为 false,那么执行 Guard2。以此类推直到某个关卡成功为止
if 表达式必须至少有一个关卡的执行结果为 true。否则就会发生异常错误。
-
为了避免异常错误,在 if 表达式的最后添加一个 true 关卡。如想让异常错误发生,就不添加。
if A > 0 -> do_this() true -> true %% 避免if表达式没有值而报错 end
case … of 表达式
-
case … of 表达式的语法:
case Expression of Pattern1 [when Guard1] -> Expr_seq1; Pattern2 [when Guard2] -> Expr_seq2; ... end
Expression 表达式;Pattern 模式匹配;Guard 关卡;Expr_seq 要执行的表达式
首先 Expression 被执行,其值为 Value。
随后 Value 轮流与 Pattern1(可选关卡)、Pattern2等模式进行匹配,直到匹配成功
发现匹配,相应的表达式序列就会执行,而表达式的结果就是 case 表达式的值
如果所有模式都不匹配,就会发生异常错误( exception )
filter( P, [H | T] ) -> case P(H) of true -> [ H | filter(P, T) ]; false -> filter(P, T) end filter( P, [] ) -> [].
-
case 不是必须的。下面只用模式匹配来定义 filter
filter( P, [H | T] ) -> filter( P(H), H, P, T ); filter( P, [] ) -> []. filter1(true, H, P, T) -> [H | filter(P, T)]; filter1(false, H, P, T) -> filter(P, T)
这个定义比较丑。必须建立额外的函数 filter1,并向 filter/2 传递所有参数
七、高阶函数
函数 | 说明 |
---|---|
Lambda 函数 | 匿名函数 |
高阶函数 | 操作其它函数的函数 |
- 函数作为高阶函数的参数
用匿名函数当参数%% 定义一个函数 1> Fn = fun(X) -> 2*X end. %% 定义一个高阶函数, %% 函数 F 作为 参数 和 返回值 2> HFn = fun(F,X) -> F(X) end. %% 调用高阶函数, 3> HFn(Fn, 9). 18
1> lists:map( fun(X) -> 2*X end , [1, 2, 3, 4]). [2,4,6,8]
- 函数作为高阶函数的返回值
就是把返回值替换成函数即可1> Fruit = [apple, pear, orange]. 2> Test = fun(L) -> (fun(X) -> lists:member(X, L) end) end. 3> IsFruit = Test(Fruit). 4> IsFruit(pear). true 5> IsFruit(dog). false
实现 for 循环
-
定义 for 循环
%% 模块名称 -module(my_lib). %% 导出模块内的方法 -export([for/3]). %% 实现 for 循环 for(Max, Max, F) -> [F(Max)]; for(I, Max, F) -> [F(I) | for(I+1, Max, F)].
-
Erlang Shell 中调用 for 循环
%% 编译 1> c(my_lib). %% 调用 2> my_lib:for(1, 10, fun(I) -> I end). [1,2,3,4,5,6,7,8,9,10] 3> my_lib:for(1, 10, fun(I) -> I*I end). [1,4,9,16,25,36,49,64,81,100]
-
在模块中使用 for 循环
%% 模块名称 -module(main). %% 导出模块内的方法 -export([run/3]). %% 导入模块 my_lib 的 for 方法 -import(my_lib, [for/3]). %% 必须在模块中的函数内调用引入的方法。 run(I, Max, F) -> my_lib:for(I, Max, F).
-
重入解析代码(reentrant parsing code)
-
解析组合器(parser combinator)
-
惰性求值器(lazy evaluator)
记录 record
返回目录
记录其实就是元组的另一种形式
使用记录,可以给元组里的各个元素关联一个名称
- 应该在下列情形里使用记录:
- 当你可以用一些预先确定且数量固定的原子来表示数据时;
- 当记录里的元素数量和元素名称不会随时间而改变时;
- 当存储空间是个问题时,典型的案例是你有一大堆元组,并且每个元组都有相同的结构。
- 语法,不是shell命令,只能用在源代码模块里使用。
记录能保存在 .erl 文件(源代码)和 .hrl 文件(包含文件,类似c语言的 .h 头文件)-record(Name, { % 以下两个键带有默认值 key1 = Default1, key2 = Default2, ... % key3 = undefined key3, ... }).
- 举例,
- 定义记录。记录一旦被定义,就可以创建该记录的实例了
% records.hrl -record(todo, {status = reminder, who = joe, text}).
- 创建和更新记录
% 在 shell 里读取 包含记录的 .hrl 文件 1> rr("records.hrl"). [todo] % 创建记录,所有的键都是原子,而且必须与记录定义里所用的一致 2> #todo{}. #todo{status = reminder, who = joe, text=undefined} 3> X1 = #todo{ status = urgent, text = "Fix errata in book"}. #todo{status = urgent, who = joe, text=undefined} % 创建的是原始记录的副本 4> X2 = #todo{ status = done }. #todo{status = done, who = joe, text = "Fix errata in book"} % 提取记录字段 5> #todo{who = W, test = Txt} = X2. #todo{status = done, who = joe, text = "Fix errata in book"} 6> W. joe 7> Txt. "Fix errata in book" % 只提取单个字段,用 . 语法 8> X2#todo.text "Fix errata in book"
- 在函数里模式匹配记录
clear_status(#todo{status=S, who=W} = R) -> % 在此函数内部, S 和 W 绑定了记录里的字段值 % R是整个记录 R#todo{status=finished} % ...
- 要匹配某个类型的记录
do_something(X) when is_record(X, todo) -> %% ...
- 忘掉记录的定义,变换元组
9> X2 #todo{status=done,who=joe,text="Fix errata in book"} 10> rf(todo). % 忘掉记录todo的定义 11> X2. {todo,done,joe,"Fix errata in book"}
- 定义记录。记录一旦被定义,就可以创建该记录的实例了
映射组 map
键-值对的关联性集合
键可以是任意的Erlang数据类型
比元组占用更多的存储空间,查找起来也更慢
-
映射组适合以下的情形:
- 当键不能预先知道时用来表示键-值数据结构;
- 当存在大量不同的键时用来表示数据;
- 当方便使用很重要而效率无关紧要时作为万能的数据结构使用;
- 用作“自解释型”的数据结构,也就是说,用户容易从键名猜出值的含义;
- 用来表示键-值解析树,例如XML或配置文件;
- 用JSON来和其他编程语言通信。
- 键和值可以是任何有效的Erlang数据类型
- 映射组在系统内部是作为有序集合存储的,打印时总是使用各键排序后的顺序,与映射组的创建方式无关
-
语法
% 创建映射组 Map = #{ Key1 => Val1, Key2 => Val2, ... KeyN => ValN }. % 更新映射组 NewMap = OldMap#{K1 := V1, ...,Kn := Vn}.
-
举例
% 创建映射组 1> F1 = #{ a => 1, b => 2 }. #{a => 1,b => 2} 2> Facts = #{ {wife, fred} => "Sue", {age, fred} => 45, {daughter, fred} => "Mary", {likes, jim} => ["Man"] }. % 替换映射组 3> F2 = F1#{ c => xx }. % 新建键值 c #{a => 1,b => 2,c => xx} 4> F3 = F1#{ c := 3 }. % 错误,变量 F1 里没有 c 这个 key 5> F3 = F2#{ c := 6 }. % ok,F3 的 c 值为 6 % 取值 6> #{ c := X } = F3. % 单字段提取 X = 6 7> #{ d := Y } = F3. % 错误,匹配不到右边的值 8> #{ Z => 1547}. % 错误,变量 Z 未绑定
-
模式匹配映射组字段
可以在函数的头部使用包含模式的映射组,前提是映射组里所有的键都是已知的% 定义一个 count_characters(Str) 函数 % 让它返回一个映射组,内含字符串里各个字符的出现次数 % 这个例子运行不起来 以后在考究 count_characters(Str) -> count_characters(Str, #{}). count_characters([H | T], #{ H => N } = X) -> count_characters(T, X#{ H := N+1 }); count_characters([H | T], X) -> count_characters(T, X#{ H => 1 }); count_characters([], X) -> X.
执行
1> count_characters("hello"). #{101=>1,104=>1,1-8=>2,111=>1}
-
方法
maps:new() -> #{} %返回一个空的映射组
erlang:is_map(M) %如果M是映射组返回true否则返回false。可以用在关卡测试或函数主体中。
map:to_list(M) -> [{K1, V1}, … ,{Kn, Vn}] %把映射组M里的所有键和值转换成一个键值列表,键值在生成的列表里严格按照升序排列。
maps:from_list([{K1, V1}, …, {Kn, Vn}]) -> M %把一个包含键值对的列表转换成一个映射组M。如果同样的键不止一次的出现,就使用列表里第一键所关联的值,后续的值都会被忽略。
maps:size(Map) -> numberOfEntries %返回映射组里的条目数量。
maps:is_key(Key, Map) -> boolean()%如果映射组包含一个键未key的项就返回true,否则返回false。
maps:get(Key, Map) -> val %返回映射组里与Key关联的值,否则抛出一个异常错误。
maps:find(Key, Map) -> {ok, Value} | error。%返回映射组与Key关联的值,否则返回error。
maps:keys(Map) -> [Key1, …, KeyN] %返回映射组所含的键列表,按升序排序
maps:remove(Key, M) -> M1%返回一个新映射组M1,除了键未Key的项(如果有的话)被移除外,其他与M一致。
maps:without([Key1, …, KeyN], M) -> M1 %返回一个新映射组M1,它是M的复制,但移除了带有[Key1,…, KeyN]列表里这些键的元素。
maps:difference(M1, M2) -> M3 %M3是M1的复制,但移除了那些与M2里的元素具有相同键的元素
%他的行为类似于下面的定义
maps:difference(M1, M2) ->
maps:without(maps:keys(M2), M1).
6. 映射组排序
映射组在比较时首先会比大小,然后再按照键的排序比较键和值。
如果A和B是映射组,那么当maps:size(A) < maps:size(B)时A < B。
如果A和B是大小相同的映射组,那么当maps:to_list(A) < maps:to_list(B)时A < B。
举个例子,A = #{age => 23, person => “jim”}小于B = # {email => “sue@somplace.com”, name => “sue”}。这是因为A的最小键(age)比B的最小键(email)更小。
当映射组与其他Erlang数据类型相比较时,因为我们认为映射组比列表或元组“更复杂”,所以映射组总是会大于列表或元组
映射组可以通过io:format里的~p选项输出,并用io:read或file:consult读取。
7. 以JSON为桥梁
有两个内置函数可以让映射组和JSON数据相互转换内置函数 说明 maps:to_json(Map) -> Bin 把一个映射组转换成二进制型,它包含用JSON表示的该映射组请。注意,不是所有的映射组都能转换成JSON数据类型。映射组里所有的值都必须是能用JSON表示的对象。例如,值不能包含的对象有fun、进程标识符和引用等。如果有任何的键或值不能用JSON表示,maps:to_json就会失败。 maps:from_json(Bin) -> Map 把一个包含JSON数据的二进制型转换成映射组。 maps:safe_from_json(Bin) -> Map 把一个包含JSON数据的二进制型转换成映射组。Bin里的任何原子必须在调用此内置函数前就已存在,否则就会抛出一个异常错误。这样做是为了防止创建大量的新原子。出于效率的原因,Erlang不会垃圾回收(garbage collect)原子,所以连续不断地添加新原子会(在很长一段时间后)让Erlang虚拟机崩溃。 上面两种定义里的Map都必须是json_map()类型的实例
- type json_map() = [{json_key(), json_value()}].
- type json_key() = atom() | binary() | io_list() 并且
- type json_value() = integer() | binary() | float() | atom() | [json_value] | json_map()
JSON对象与Erlang值的映射关系如下。
- SON的数字用Erlang的整数或浮点数表示。
- JSON的字符串用Erlang的二进制型表示。
- JSON的列表用Erlang的列表表示。
- JSON的true和false用Erlang的原子true和false表示。
- JSON的对象用Erlang的映射组表示,但是有限制:映射组里的键必须是原子、字符串或二进制型,而值必须可以用JSON的数据类型表示。当来回转换JSON数据类型时,应当注意一些特定的转换限制。Erlang对整数提供了无限的精度。所以,Erlang会很自然地把映射组里的某个大数转换成JSON数据里的大数,而解码此JSON数据的程序不一定能理解它。
-----------------------------
列表推导
Quicksort 快速排序
-
定义快速排序
-module(my_lib). -export([qsort/1]). qsort( [] ) -> []; qsort( [ Pivot | T ] ) -> qsort( [ X || X <- T, X < Pivot ] ) ++ [ Pivot ] ++ qsort( [ X || X <- T, X>= Pivot ] ).
++ 操作符表示连接列表,代码优雅,但效率不行。
调用
1> c(my_lib). 2> L = [23,6,2,9,27,400,78,45,61,82,14] 3> my_lib:qsort(L). [2,6,9,14,23,27,45,61,78,82,400]
1、参数 [Povot | T] 产生绑定
#伪代码 [ Pivot | T ] = Pivot = 23, T = [6,2,9,27,400,78,45,61,82,14]
2、将列表 T 分成两个表,一个包含 T 里所有小于 Pivot 的元素,另一个包含所有大于等于 Pivot 的元素:
#伪代码 Smaller = [ X || X <- T, X < Pivot ]. = [6,2,9,14] Bigger = [ X || X <- T, X >= Pivot ]. = [27,400,78,45,61,82]
3、递归排序 Smaller 和 Bigger,并将它们与 Pivot 合并:
#伪代码 qsort( [6,2,9,14] ) ++ [23] ++ qsort( [27,400,78,45,61,82] ) = [2,6,9,14] ++ [23] ++ [27,45,61,78,82,400] = [2,6,9,14,23,27,45,61,78,82,400]
毕达哥拉斯三元数组
pythag(N) ->
[ {A,B,C} ||
A <- lists:seq(1, N), %% A 提取列表[1,..N]里元素
B <- lists:seq(1, N), %% B 提取列表[1,..N]里元素
C <- lists:seq(1, N), %% C 提取列表[1,..N]里元素
A+B+C =< N, %% 条件1:A 加 B 加 C 小于等于 N
A*A+B*B =:= C*C %% 条件2:并且 A方 加 B方 等于 C方
].
回文构词
perms( [] ) -> [ [] ];
perms( L ) -> [ [ H | T ] || H <- L, T <- perms( L -- [ H ] ) ],
1、从 L 里 提取 H,然后从 perms( L – [H] )里提取 T,最后返回 [H | T]
2、H <- L 相当于一个for循环,在每次循环中又执行一次 T <- perms( L – [ H ] ) ,相当于循环嵌套,又相当于递归
位语法
- 寻找MPEG音频数据里的同步点
MPEG音频数据是由许多的帧组成的。每一帧都有它自己的帧头,后面跟着音频信息。由于没有文件头,所以原则上可以把一个MPEG文件切成多个片段,而每一段都可以独立播放。任何读取MPEG流的软件都应当能找到头帧,然后同步MPEG数据。
MPEG头以一段11位的帧同步(frame sync)信息开头,它包含11个连续的1位,后面跟着描述后续数据的信息:AAAAAAAA AAABBCCD EEEEFFGH IIJJKLMM
AAAAAAAAAAA 同步字(11位,全为1)
BB 2位的MPEG音频版本ID
CC 2位的层描述
D 1位保护位
…
知道了从A到M的值,就能计算出一个MPEG帧的总长度
为了找到同步点,首先假设我们成功定位到了某个MPEG头的起始位置,然后试着计算该帧的长度。之后可能会发生以下某一种情况。
- 假设正确,因此当向前跳过帧长后,会找到另一个MPEG头。
- 假设错误。可能没有定位到标记MPEG头起始位置的位序列(由11个连续的1组成)上,或者因为字的格式不正确而无法计算帧长。
- 假设错误,但定位到的若干字节音乐数据碰巧和MPEG头的起始位置很相似。在这种情况下,可以计算帧长,但是当向前跳过这段距离后,我们无法找到新的MPEG头。
为了确保万无一失,我们将寻找三个连续的MPEG头。同步的程序如下所示: - 构建微软通用对象文件格式(Microsoft Common Object File Format,简称COFF)的二进制数据文件。打包和解包二进制数据文件(例如COFF)的典型做法是使用二进制型和二进制模式匹配
- 解包一个IPv4数据报(datagram)
实现简单的处理列表的方法
list 递归求和
- 定义递归求和函数 sum/1
两个 sum 的顺序不重要,因为两种情况是互斥的。%% 模块名称 -module(my_lib). %% 导出模块内的方法 -export([sum/1]). sum( [H | T] ) -> H + sum(T); %% 递归调用 sum( [] ) -> 0. %% 为空,返回 0
- 在 Erlang Shell 调用
%% 编译 1> c(my_lib). %% 调用 2> my_lib:sum([1,2,3,4,5]). 15
遍历 list
- 定义遍历 List 函数 map/2
%% 模块名称 -module(my_lib). %% 导出模块内的方法 -export([map/2]). %% 两个参数,一个处理函数,一个要处理的 List map(_, []) -> []; %% 空 List 返回空 List map(F, [H|T]) -> [F(H)|map(F,T)]. %% 递归调用
- 在 Erlang Shell 中调用
1> c(my_lib). 2> my_lib:map(fun(X) -> 2*X end, [1,2,3,4,5]). [2,4,6,8,10]
计算总和
- 文件 shop.erl
-module(shop). -export([cost/1]). cost(oranges) -> 5; cost(newspaper) -> 8; cost(apple) -> 2; cost(pears) -> 9; cost(milk) -> 7;
- 文件 shop1.erl
-module(shop1). -export([totle/1]). total([{What, N} | T]) -> shop:cost(What) * N + total(T); total([]) -> 0.
- 执行 shop1.erl
语句匹配所得值为:What = milk,N = 3,T = []$ erl 1> c(shop). {ok,shop} 2> c(shop1). {ok,shop1} 3> shop1:total([]). 0 4> shop1.total([{milk, 3}]). 21
执行语句 shop:cost(What) * N + total(T)。
即:shop:cost(milk) => 7 * 3 + total([]) => 0 = 21
语句匹配所得值为:What = pears,N = 6,T = [{milk, 3}]5> shop1:total([{pears, 6}, {milk, 3}]). 75
6> Buy = [{oranges, 4}, {newspaper, 1}, {apple, 10}, {pears, 6}, {milk, 3}]. 7> shop1:total(Buy). 123
归集器 accumulator
```erlang
% lib_misc.erl
% 不推荐,这样会遍历2次列表
odds_and_evens1(L) ->
Odds = [X || X <- L, (X rem 2) =:= 1],
Evens = [X || X <- L, (X rem 2) =:= 0],
{Odds, Evens}.
% 推荐,只遍历列表1次,把奇偶数分别添加到适合的列表里。
odds_and_evens2(L) -> odds_and_evens_acc(L, [], []).
odds_and_evens_acc([H|T], Odds, Evens) ->
case (H rem 2) fo
1 -> odds_and_evens_acc(T, [H|Odds], Evens);
0 -> odds_and_evens_acc(T, Odds, [H|Evens]);
end;
odds_and_evens_acc([], Odds, Evens) ->
{ lists:reverse(Odds), lists:reverse(Evens) }. % 得出得结果是反得,需要逆转
```
执行
```shell
1> lib_misc:odds_and_evens1([1,2,3,4,5,6]).
{[1,3,5], [2,4,6]}
2> lib_misc:odds_and_evens2([1,2,3,4,5,6]).
{[1,3,5], [2,4,6]}
```
apply
此内置函数能调用某个模块里的某个函数,并向它传递参数
与直接调用函数的区别在于模块名和/或函数名可以是动态计算得出的
尽量避免使用 apply
```apply(Mod, Func, [Arg1, Arg2, ..., ArgN])```动态调用
```Mod:Func(Arg1, Arg2, ..., ArgN)```效果相同,静态调用
1. 所有的Erlang内置函数也可以通过apply进行调用,
```shell
1> apply(erlang, atom_to_list, [hello]).
```
2. Mod 参数不必是一个原子,也可以是一个元组
```{Mod, P1, P2, ..., Pn}:Func(A1,A2,...An)```等同于
```Mod:Func(A1,A2,...,An,{Mod,P1,P2,...,Pn})```
元数
一个函数的元数(arity)是该函数所拥有的参数数量。
同一模块里的两个名称相同、元数不同的函数是完全不同的函数。
Erlang程序员经常将名称相同、元数不同的函数作为辅助函数使用。
```erlang
% 元数为 1
sum(L) -> sum(L, 0). % 累加列表 L 里的所有元素
% 元数为 2 辅助函数,不导出来隐藏它们
sum([], N) -> N;
sum([H|T], N) -> sum(T, H+N).
```