0

When writing a function like 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).

one cannot help but notice that tail call optimization is not straight forward however writing it in continuation parsing style is:

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).

Or perhaps even defunctionalized:

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}).

Timing these three implementations I found that execution time is, as expected, the same.

My question is, is there a way to get more detailed knowledge than this? How do I for instance get the memory usage of executing the function - am I avoiding any stack memory at all?

What are the standart tools for inspecting these sorts of things?

The questions are again, how do I mesure the stack heights of the functions, how do I determine the memory usage of a function call on each of them, and finally, which one is best?

hammar
  • 138,522
  • 17
  • 304
  • 385
Martin Kristiansen
  • 9,875
  • 10
  • 51
  • 83

2 Answers2

3

My solution is to just inspect the code with my eyes. Over time, you learn to spot if the code is in tail-call style. Usually, I don't care too much about it, unless I know the size of the structure passing through that code to be huge.

It is just by intuition for me. You can inspect the stack size of a process with erlang:process_info/2. You can inspect the runtime with fprof. But I only do it as a last resort fix.

I GIVE CRAP ANSWERS
  • 18,739
  • 3
  • 42
  • 47
  • One of the things I'm interested in figuring out is how much LCO work erlang does without me asking, and when it makes sense to do it your self - and I just wanted to inspect that. I know that the last version will be optimized, but what about the first one, will the `X * F(X -1, F)` mean that we get a stack of X's that are then multiplied together back though the stack? – Martin Kristiansen May 27 '13 at 16:01
  • 1
    No. Olivier would probably have written `(* X (F (-1 X) F))` or something like that in Scheme and then it is pretty clear that the last thing that function clause is doing is a multiplication and not a function call. Hence, the TCO/LCO can not apply and it doesn't get optimized. The *other* thing to look out for is try...catch which pushes a handler on the stack which unwinds it even if your call is in the tail position. – I GIVE CRAP ANSWERS May 27 '13 at 17:20
  • 1
    And I am not the biggest fan of Scheme :P – I GIVE CRAP ANSWERS May 27 '13 at 21:00
2

This doesn't answer your question, but why have you written the code like that? It is not very Erlangy. You generally don't use an explicit CPS unless there is a specific reason for it, it is normally not needed.

As @IGIVECRAPANSWERS says you soon learn to see tail-calls, and there are very few cases where you actually MUST use it.

EDIT: A comment to the comment. No there is no direct way of checking whether the compiler has used LCO or not. It does exactly what you tell it to and assumes you know what you are doing, and why. :-) However, you can be certain that it does when it can, but that it is about it. The only way to check is to look at the stack size of a process to see whether it is growing or not. Unfortunately if you got it wrong in the right place the process can grow very slowly and be hard to detect except over a long period of time.

But again there are very few places where you really need to get the LCO right.

P.S. You use the term LCO (Last Call Optimisation) which is what I learnt way back when. Now, however, "they" seem to use TCO (Tail Call Optimisation) instead. That's progress. :-)

rvirding
  • 20,848
  • 2
  • 37
  • 56
  • Your right my code is not very erlangy, I started my functional programming days doing sML. However The question remains the same. Mainly is there a way to inspect weather LCO is detected? – Martin Kristiansen May 27 '13 at 15:19
  • Btw, I learned the term Last Call Optimization from Professor Olivier Danvy, the most quoted man in science, and I'm not going to change it becaurse of some fab. :-) – Martin Kristiansen May 27 '13 at 15:54