查看源代码 函数
函数声明语法
一个函数声明是一系列用分号分隔的函数子句,并以句点(.
)结尾。
一个函数子句由一个子句头和一个子句体组成,两者之间用 ->
分隔。
一个子句头由函数名、一个参数列表以及一个可选的以关键字 when
开头的保护序列组成。
Name(Pattern11,...,Pattern1N) [when GuardSeq1] ->
Body1;
...;
Name(PatternK1,...,PatternKN) [when GuardSeqK] ->
BodyK.
函数名是一个原子。每个参数都是一个模式。
参数的个数 N
是函数的元数。一个函数由模块名、函数名和元数唯一确定。也就是说,在同一个模块中,名称相同但元数不同的两个函数是两个不同的函数。
模块 mod
中名为 f
且元数为 N
的函数通常表示为 mod:f/N
。
一个子句体由一系列用逗号(,
)分隔的表达式组成。
Expr1,
...,
ExprN
有效的 Erlang 表达式和保护序列在 表达式 中描述。
示例
fact(N) when N > 0 -> % first clause head
N * fact(N-1); % first clause body
fact(0) -> % second clause head
1. % second clause body
函数求值
当调用函数 M:F/N
时,首先定位该函数的代码。如果找不到该函数,则会发生 undef
运行时错误。请注意,该函数必须导出才能在其定义的模块之外可见。
如果找到该函数,则会按顺序扫描函数子句,直到找到一个满足以下两个条件的子句:
- 子句头中的模式可以成功地与给定的参数匹配。
- 保护序列(如果有)为真。
如果找不到这样的子句,则会发生 function_clause
运行时错误。
如果找到这样的子句,则会计算相应的子句体。也就是说,按顺序计算主体中的表达式,并返回最后一个表达式的值。
考虑函数 fact
-module(mod).
-export([fact/1]).
fact(N) when N > 0 ->
N * fact(N - 1);
fact(0) ->
1.
假设您要计算 1 的阶乘
1> mod:fact(1).
从第一个子句开始求值。模式 N
与参数 1 匹配。匹配成功,并且保护条件(N > 0
)为真,因此 N
绑定为 1,并计算相应的函数体
N * fact(N-1) => (N is bound to 1)
1 * fact(0)
现在,调用 fact(0)
,并再次按顺序扫描函数子句。首先,模式 N
与 0 匹配。匹配成功,但保护条件(N > 0
)为假。其次,模式 0
与参数 0
匹配。匹配成功,并计算函数体
1 * fact(0) =>
1 * 1 =>
1
求值成功,mod:fact(1)
返回 1。
如果使用负数作为参数调用 mod:fact/1
,则没有子句头匹配。会发生 function_clause
运行时错误。
尾递归
如果函数体的最后一个表达式是一个函数调用,则执行尾递归调用。这是为了确保不消耗系统资源,例如调用堆栈。这意味着使用尾递归调用的无限循环不会耗尽调用堆栈,并且(原则上)可以永远运行。
示例
loop(N) ->
io:format("~w~n", [N]),
loop(N+1).
早期的阶乘示例是一个反例。它不是尾递归的,因为在递归调用 fact(N-1)
的结果上进行了乘法运算。
内置函数 (BIF)
内置函数(BIF)是在运行时系统中用 C 代码实现的。BIF 执行在 Erlang 中难以或不可能实现的操作。大多数 BIF 属于模块 erlang
,但也有一些 BIF 属于其他几个模块,例如 lists
和 ets
。
属于 erlang
的最常用的 BIF 是自动导入的。它们不需要以模块名称为前缀。哪些 BIF 是自动导入的在 ERTS 中的 erlang
模块中指定。例如,诸如 atom_to_list
之类的标准类型转换 BIF 和允许在保护序列中使用的 BIF 可以不指定模块名称进行调用。
示例
1> tuple_size({a,b,c}).
3
2> atom_to_list('Erlang').
"Erlang"