查看源代码 函数

函数声明语法

一个函数声明是一系列用分号分隔的函数子句,并以句点(.)结尾。

一个函数子句由一个子句头和一个子句体组成,两者之间用 -> 分隔。

一个子句由函数名、一个参数列表以及一个可选的以关键字 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 运行时错误。请注意,该函数必须导出才能在其定义的模块之外可见。

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

  1. 子句头中的模式可以成功地与给定的参数匹配。
  2. 保护序列(如果有)为真。

如果找不到这样的子句,则会发生 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 属于其他几个模块,例如 listsets

属于 erlang 的最常用的 BIF 是自动导入的。它们不需要以模块名称为前缀。哪些 BIF 是自动导入的在 ERTS 中的 erlang 模块中指定。例如,诸如 atom_to_list 之类的标准类型转换 BIF 和允许在保护序列中使用的 BIF 可以不指定模块名称进行调用。

示例

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