编写像factorial这样的函数时:
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}).
对这三个实现进行定时我发现执行时间和预期的一样。
我的问题是,有没有办法获得比这更详细的知识? 例如,我如何获取执行函数的内存使用情况 - 我是否完全避免任何堆栈内存?
检查这些东西的标准工具是什么?
问题又是如何,我如何确定函数的堆栈高度,如何确定函数调用的内存使用情况,最后哪一个最好?
答案 0 :(得分:3)
我的解决方案是用眼睛检查代码。随着时间的推移,您将学会发现代码是否采用尾调用方式。通常,我不太关心它,除非我知道通过该代码的结构的大小是巨大的。
这只是凭直觉对我而言。您可以使用erlang:process_info/2
检查进程的堆栈大小。您可以使用fprof
检查运行时。但我只把它作为最后的手段来解决。
答案 1 :(得分:2)
这不能回答你的问题,但你为什么要编写这样的代码呢?它不是非常Erlangy。除非有特定原因,否则通常不使用明确的CPS,通常不需要。
正如@IGIVECRAPANSWERS所说,你很快就会学会看到尾部调用,并且很少有你真正必须使用它的情况。
编辑:对评论发表评论。没有没有直接的方法来检查编译器是否使用了LCO。它完全符合您的要求,并假设您知道自己在做什么,以及为什么。 :-)但是,你可以肯定它确实可以,但它是关于它的。检查的唯一方法是查看进程的堆栈大小,以查看它是否正在增长。不幸的是,如果你在正确的地方弄错了,那么这个过程会非常缓慢地增长,除了很长一段时间之外很难被发现。
但是,很少有地方你真的需要让LCO正确。
P.S。您使用术语LCO(最后呼叫优化),这是我在以后学到的方法。然而,现在,“他们”似乎使用TCO(尾调用优化)。那是进步。 : - )