Erlang和函数式编程的不重要问题

4
我偶然发现了这个问题,意识到自己忘记了很多非过程化编程课的内容。
当我试图理解代码时,它似乎显得冗长无比,所以我尝试缩短它。这段代码是否与原始代码执行相同的功能?
merge([X|Xs], Ys) -> [X | merge(Ys, Xs)];
merge([], []) -> [].

我以前从未使用过Erlang,所以可能会犯一些语法错误 :-)

2个回答

3

是的,它可以正常工作。而且呈现方式更加优雅。然而,如果我学得正确,不使用Zs变量作为累加器会使其不是尾递归,因此效率较低。此外,使用累加器的reverse比按正确顺序附加更有效。我认为这就是原始代码在某些情况下更合适的原因。但在效率不重要的情况下,可读性应该高于效率。

也许:

merge(Xs, Ys) -> lists:reverse(merge(Xs, Ys, [])).

merge([X|Xs], Ys, Zs) -> merge(Ys, Xs, [X|Zs]);
merge([], [], Zs) -> Zs.

这将会把原始的高效性与您的简洁易懂性合二为一。

2
你可以更进一步:
merge(Xs, Ys) -> lists:reverse(merge1(Xs, Ys, [])).

merge1([], [], Zs)             -> Zs.
merge1([X | Xs], [Y | Ys], Zs) -> merge1(Xs, Ys, [X, Y | Zs]).

这比feonixrift的建议有显著优势,因为你没有改变参数顺序(这违反了最少惊讶原则)。
在这种情况下,给辅助函数(在本例中为merge1)一个不同的名称也是很好的实践,因为这样更容易发现arity的变化。特别是如果merge/2没有被导出,而merge1/3没有被导出。它基本上是说:“我只是一个helper fn,请不要直接调用我!”
此外,将所需的终止子句写在第一位也很方便,因为这使递归的本质明确 - 读取函数定义时,您会立即知道该函数在列表耗尽时终止。

如果我将一个列表与空列表合并,这段代码会返回原始列表吗?对我来说,模式匹配看起来不完整。 - Nathan Shively-Sanders
原始任务是针对两个长度相同的列表。 - cube
立方体是正确的 - 如果您尝试在两个长度不相等的列表上运行原始代码,它将失败。那里的终端子句匹配mergeR([], [], Zs)。如果Xs和Ys的长度不同,则会出现错误,因为一个列表在另一个列表之前耗尽。 - Gordon Guthrie

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接