我对 Erlang 还很陌生,并试图解决无法更改变量的问题。假设我创建了一个堆栈并想要添加一个新元素。如果我无法为列表分配新值,我将如何更新堆栈?我每次都需要创建一个新列表吗?
例如,我在想 Push 可能看起来像
List = [X|List].
Run Code Online (Sandbox Code Playgroud)
然后流行音乐将是
[Head|Tail] = List
Head
List = Tail
Run Code Online (Sandbox Code Playgroud)
当然,这是行不通的,因为我无法更改 List 的值,这就是我的问题。任何帮助表示赞赏。
Erlang 不能在函数内部产生副作用,这是函数式编程语言中常见的功能。改变变量状态是一个副作用。Erlang 中的所有状态更改都被进程和消息传递隐藏,即所谓的参与者模型。
“更改”变量的常见方法是让函数使用更改后的变量调用自身,这称为递归。例如,对列表的所有元素求和:
sum([]) -> 0;
sum([H|Tail]) -> H + sum(Tail).
Run Code Online (Sandbox Code Playgroud)
更好的是让你的函数尾部递归,这意味着它们将自己称为函数体中的最后一条指令。这将节省内存,因为并非所有函数调用都需要保留在堆栈上(尾调用优化)。相同的示例,但使用尾递归:
sum([], Acc) -> Acc;
sum([H|Tail], Acc) -> sum(Tail, Acc + H).
sum(L) -> sum(L, 0).
Run Code Online (Sandbox Code Playgroud)
在此示例中,我引入了一个累加器变量来传递中间结果。
如何使程序没有副作用并不总是显而易见的,特别是当您尝试用过程术语(如 C 或 Java)来考虑问题时。它需要实践,可能还需要在更理论的层面上理解函数式编程的意愿。
纯函数式编程语言根本不可能有任何副作用;函数的返回值必须仅基于函数的输入参数,并且函数的唯一输出必须是返回值。Erlang 的情况并非如此。子句recieve和!运算符用于函数内部的输入和输出;副作用。外部状态可以作为进程来保存,您可以向其发送消息并获取回复。
下面是如何创建一个可以通过消息传递更改其值的变量的示例(尝试弄清楚是否var_proc是尾递归!):
var_proc(Value) ->
receive
{get, Pid} ->
Pid ! {value, Value},
var_proc(Value);
{set, New} ->
var_proc(New)
end.
var_start(Init) ->
spawn(?MODULE, var_proc, [Init]).
var_set(Pid, Value) ->
Pid ! {set, Value}.
var_get(Pid) ->
Pid ! {get, self()},
receive
{value, Value} -> Value
end.
Run Code Online (Sandbox Code Playgroud)
这是如何使用它的示例(我将该模块称为“sum”):
13> V = sum:var_start(6).
<0.72.0>
14> sum:var_get(V).
6
15> sum:var_set(V, 10).
{set,10}
16> sum:var_get(V).
10
Run Code Online (Sandbox Code Playgroud)
更多示例和一些动机可以在Erlang 文档的并发编程一章中找到。