recursion函数挂起,Erlang

我正在教我自己的二郎。 一切都很顺利,直到我发现这个function的问题。

-module(chapter). -compile(export_all). list_length([]) -> 0; list_length([_|Xs]) -> 1+list_length([Xs]). 

这是从教科书中拿出来的。 当我运行这个代码使用OTP 17时,它只是挂起,这意味着它只是如下所示。

 1> c(chapter). {ok,chapter} 2> chapter:list_length([]). 0 3> chapter:list_length([1,2]). 

在任务pipe理器中查看时,Erlang OTP使用200 Mb到330 Mb的内存。 是什么原因造成的

它不是终止的,因为你在每一种情况下都创建一个新的非空列表: [Anything]总是一个非空列表,即使该列表包含一个空列表作为其唯一成员( [[]]是一个非空列表)一个成员的空白列表)。

一个正确的列表终止如下: [ Something | [] ] [ Something | [] ]

所以考虑到这一点…

 list_length([]) -> 0; list_length([_|Xs]) -> 1 + list_length(Xs). 

在大多数功能语言中,“正确列表”都是cons-style列表。 查看关于“cons”和关于列表的Erlang文档 的维基百科条目 ,然后冥想您在示例代码中看到的列表操作示例。

笔记

  1. 把空白放在操作员周围是一件好事; 它会阻止你使用箭头和二进制语法操作符相互混淆,并避免其他一些含糊之处(而且它更容易阅读)。

  2. 正如Steve指出的那样,你注意到的内存爆炸是因为当你的函数是递归的时候,它不是尾递归 – 也就是说, 1 + list_length(Xs)留下待完成的工作,必须在栈上留下引用。 有任何东西要添加1它必须完成list_length/1执行,返回一个值,在这种情况下记住待处理的值多少有成员在列表中。 阅读Steve的答案,了解如何使用累加器值来写入尾递归函数。

由于OP正在学习Erlang,因此需要注意的是, list_length/1函数由于它的加法操作而不适合tail调用优化 ,这要求运行时间递归地调用函数,取其返回值,加1,返回结果。 这需要堆栈空间,这意味着如果列表足够长,则可以用尽堆栈。

考虑这种方法,而不是:

 list_length(L) -> list_length(L, 0). list_length([], Acc) -> Acc; list_length([_|Xs], Acc) -> list_length(Xs, Acc+1). 

这种在Erlang代码中非常常见的方法是在list_length/1创建一个累加器来保存长度值,将其初始化为0并将其传递给执行递归的list_length/2 。 每次调用list_length/2 ,累加器都会增加,当列表为空时, list_length/2的第一个子句返回累加器。 但是请注意,这里的加法操作发生递归调用发生之前 ,这意味着调用是真正的tail调用,因此不需要额外的堆栈空间。

对于非初学者的Erlang程序员来说,用erlc -S编译这个模块的原始版本和修改版本并检查生成的Erlang汇编程序可能是erlc -S 。 使用原始版本时,汇编程序包含对堆栈空间的allocate调用,并使用call进行递归调用,其中call是正常函数调用的指令。 但是对于这个修改的版本,没有allocate调用生成,而不是使用call它执行递归使用call_only ,这是优化的尾部调用。