6  函数

6 函数

函数声明是由以分号分隔的函数子句序列组成,并以句点 (.) 结束。

函数子句由子句头和子句体组成,以 -> 分隔。

子句由函数名、参数列表和以关键字 when 开头的可选守卫序列组成

Name(Pattern11,...,Pattern1N) [when GuardSeq1] ->
    Body1;
...;
Name(PatternK1,...,PatternKN) [when GuardSeqK] ->
    BodyK.

函数名是原子。每个参数都是一个模式。

参数的个数 N 是函数的元数。函数由模块名、函数名和元数唯一定义。也就是说,在同一个模块中具有相同名称但元数不同的两个函数是两个不同的函数。

模块 m 中名为 f 的函数,元数为 N,通常表示为 m: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 运行时错误。请注意,该函数必须导出才能在定义它的模块外部可见。

如果找到该函数,将会顺序扫描函数子句,直到找到满足以下两个条件的子句

  1. 子句头中的模式可以成功地与给定的参数匹配。
  2. 如果存在,守卫序列为真。

如果找不到这样的子句,将会发生 function_clause 运行时错误。

如果找到这样的子句,将会评估相应的子句体。也就是说,将顺序评估体中的表达式,并返回最后一个表达式的值。

考虑函数 fact

-module(m).
-export([fact/1]).

fact(N) when N>0 ->
    N * fact(N-1);
fact(0) ->
    1.

假设您要计算 1 的阶乘

1> m: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

评估已成功完成,m:fact(1) 返回 1。

如果用负数作为参数调用 m:fact/1,则没有子句头匹配。将发生 function_clause 运行时错误。

如果函数体的最后一个表达式是函数调用,则会执行尾递归调用。这是为了确保不会消耗系统资源(例如调用栈)。这意味着,如果使用尾递归调用,可以执行无限循环。

示例

loop(N) ->
    io:format("~w~n", [N]),
    loop(N+1).

前面的阶乘示例可以作为反例。它不是尾递归,因为对递归调用 fact(N-1) 的结果进行了乘法操作。

BIF 在运行时系统中以 C 代码实现。BIF 执行在 Erlang 中难以或不可能实现的操作。大多数 BIF 属于模块 erlang,但也有一些 BIF 属于其他几个模块,例如 listsets

属于 erlang(3) 的最常用的 BIF 是自动导入的。它们不需要以模块名称为前缀。哪些 BIF 是自动导入的,在 ERTS 中的 erlang(3) 模块中指定。例如,可以调用标准类型转换 BIF(如 atom_to_list)和在守卫中允许的 BIF,而无需指定模块名称。

示例

1> tuple_size({a,b,c}).
3
2> atom_to_list('Erlang').
"Erlang"

请注意,通常是指当谈论“BIF”时,自动导入的 BIF 集。