0

在编写像阶乘这样的函数时:

fac(Val) when is_integer(Val)->
    Visit = fun (X, _F) when X < 2 -> 
                    1;
                (X, F) ->
                    X * F(X -1, F)
            end,
    Visit(Val, Visit).

不禁会注意到尾调用优化并不是直截了当的,但是以连续解析的方式编写它是:

fac_cps(Val) when is_integer(Val)->
    Visit = fun (X, _F, K) when X < 2 -> 
                    K (1);
                (X, F, K) ->
                    F(X-1, F, fun (Y) -> K(X * Y) end)
            end,
    Visit(Val, Visit, fun (X) -> X end).

或者甚至可能被取消功能:

fac_cps_def_lambdas({lam0}, X) ->
    X;
fac_cps_def_lambdas({lam1, X, K}, Y) ->
    fac_cps_def_lambdas(K, X*Y).


fac_cps_def(X) when is_integer(X) ->
    fac_cps_def(X, {lam0}).

fac_cps_def(X, K) when X < 2 -> 
    fac_cps_def_lambdas(K,1);
fac_cps_def(X, K) ->
    fac_cps_def(X-1, {lam1, X, K}).

对这三个实现进行计时,我发现执行时间和预期的一样。

我的问题是,有没有办法获得比这更详细的知识?例如,我如何获得执行函数的内存使用情况——我是否完全避免了任何堆栈内存?

检查这些东西的标准工具是什么?

问题又是,我如何测量函数的堆栈高度,如何确定每个函数调用的内存使用情况,最后,哪一个最好?

4

2 回答 2

3

我的解决方案是只用我的眼睛检查代码。随着时间的推移,您将学会发现代码是否采用尾调用样式。通常,我不太关心它,除非我知道通过该代码的结构的大小很大。

这对我来说只是直觉。您可以使用 .检查进程的堆栈大小erlang:process_info/2。您可以使用fprof. 但我只是把它作为最后的解决办法。

于 2013-05-27T09:21:19.797 回答
2

这并不能回答您的问题,但是您为什么要编写这样的代码?这不是很 Erlangy。除非有特定原因,否则您通常不会使用显式 CPS,通常不需要。

正如@IGIVECRAPANSWERS 所说,您很快就会学会看到尾声,而且在极少数情况下您实际上必须使用它。

编辑:对评论的评论。不,没有直接的方法可以检查编译器是否使用了 LCO。它完全按照你告诉它的去做,并假设你知道你在做什么,以及为什么。:-) 但是,您可以确定它会在可能的情况下发生,但它就是这样。检查的唯一方法是查看进程的堆栈大小以查看它是否在增长。不幸的是,如果你在正确的地方弄错了,这个过程可能会发展得非常缓慢,而且除非经过很长一段时间,否则很难被发现。

但同样,您真正需要正确使用 LCO 的地方很少。

PS您使用术语LCO(Last Call Optimisation),这是我以前学到的。然而,现在“他们”似乎改用 TCO(尾调用优化)。这就是进步。:-)

于 2013-05-27T12:10:43.443 回答