Java 和 Erlang 是不同的野兽。我不建议在学习 Erlang 时尝试与 Java 进行比较,特别是如果 Java 是您目前唯一知道的语言。您发布的代码是“函数式编程”范式的一个很好的例子。我建议您对该主题进行一些阅读,以帮助您了解正在发生的事情。要尝试就 Erlang 进行分解,您需要了解 Erlang 函数与 Java 方法完全不同。
在 Java 中,您的方法签名由方法名称及其参数的类型组成。返回类型也可能很重要。像您编写的函数这样的 Java increment 方法可能会写成 List<Integer> increment(List<Integer> input)。 Java 方法的主体可能会一次遍历列表中的一个元素,并将每个元素设置为自身加一:
List<Integer> increment(List<Integer> input) {
for (int i = 0; i < input.size; i++) {
input.set(i, input.get(i) + 1);
}
}
Erlang 与此几乎没有共同之处。首先,erlang 函数的“签名”是函数的名称和数量。 Arity 表示函数接受多少个参数。所以你的增量函数被称为increment/1,这就是它的独特签名。在函数名后面的括号内编写参数列表的方式与参数类型的关系较少,而与传递给它的数据的模式有关。像increment([]) -> ... 这样的函数只能通过传递空列表[] 才能成功调用。同样,函数increment([Item]) -> ... 只能通过传递一个包含一项的列表才能成功调用,而increment([Item1, Item2]) -> ... 必须传递一个包含两项的列表。这种将数据与模式匹配的概念非常恰当地称为“模式匹配”,您会在许多函数式语言中找到它。在 Erlang 函数中,它用于选择要执行的函数的哪个头部。这与 Java 的方法重载大致相似,在这种重载中,您可以拥有许多具有相同名称但参数类型不同的方法;然而,Erlang 函数头中的模式可以将变量绑定到与模式匹配的不同参数。
在您的代码示例中,函数increment/1 有两个头。仅当您将空列表传递给函数时,才会执行第一个 head。仅当您将非空列表传递给函数时,才会执行第二个头。发生这种情况时,会绑定两个变量 H 和 T。 H 绑定到列表的第一项,T 绑定到列表的其余部分,即除第一项之外的所有内容。这是因为模式[H|T] 匹配一个非空列表,包括一个包含一个元素的列表,在这种情况下T 将绑定到空列表。这样绑定的变量可以在函数体中使用。
你的函数体是在 Erlang 中迭代列表以生成新列表的一种非常典型的形式。这是典型的,因为与 Java 的另一个重要区别是 Erlang 数据是不可变的。这意味着没有像我在上面的 Java 代码中那样“设置列表的元素”这样的概念。如果要更改列表,则必须构建一个新列表,这就是您的代码所做的。它有效地表示:
- 空列表递增的结果就是空列表。
- 递增非空列表的结果是:
- 取列表的第一个元素:
H。
- 增加列表的其余部分:
increment(T)。
- 将
H+1 添加到列表其余部分递增的结果中。
请注意,您要小心如何在 Erlang 中构建列表,否则最终可能会浪费大量资源。 List Handling User's Guide 是了解这一点的好地方。另请注意,此代码使用称为“递归”的概念,这意味着函数调用自身。在包括 Java 在内的许多流行语言中,递归的用处有限,因为每个新函数调用都会添加一个堆栈帧,而堆栈帧的可用内存空间相对有限。 Erlang 和许多函数式语言都支持一种称为“尾调用消除”的功能,该功能允许正确编写的代码无限期地递归而不会耗尽任何资源。
希望这有助于解释事情。如果您可以提出更具体的问题,您可能会得到更好的答案。