简体   繁体   中英

How do I check where my code gets stuck in Erlang?

I'm trying to write a function that receives a list, finds the highest value integer in the list, and then divides all the other integers in the list by that value.

Unfortunately, my code gets stuck somewhere. If this were python for example I could easily write a couple different "print"s and see where it gets stuck. But how do you do that in Erlang?

Here is the code.

highest_value([], N) ->
    if
        N =:= 0 ->
            'Error! No positive values.'
    end,
    N;
highest_value([H|T], N) when H > N, H > 0 ->
    highest_value([T], H);
highest_value([_|T], N) ->
    highest_value([T], N).

divide(_L) -> [X / highest_value(_L, 0) || X <- _L].

For prints you could just use io:format/2 . Same thing.

highest_value([H|T], N) when H > N, H > 0 -> 
   io:format(">>> when H bigger than N~n"),
   io:format(">>> H: ~p,  T: ~p, N: ~p ~n", [H, T, N]),
   highest_value([T], H);
highest_value(List) ->
   highest_value(List, 0).

EDIT

One thing you are getting wrong is [H | T] [H | T] syntax. H , or head, is the first element in the list. T stands for tail, or "rest of the list". And like the name suggests, tail is a list (could be an empty list, but a list nonetheless). So when you are doing you recursion you don't need to put T inside a new list.

highest_value([H|T], N) when H > N -> 
     highest_value(T, H); 

highest_value([_|T], N) -> 
     highest_value(T, N).

In your old code you called:

   highest_value([T], N).

which created a new list with one element, like [[2,3,4,5]] . If you head-tail this, you get this only-element-list as the head, and an empty list as the tail.


Also, in your first function clause you have an atom 'Error! No positive values.' 'Error! No positive values.' (singe quotes means this is just a long atom, and not a string) which is never returned (you will always return N ). If you would like to return either some atom, or N , depending on value of N you could just extend your use of function clauses

highest_value([], 0) ->
   'Error! No positive values.'
highest_value([], N) ->
   N;
[...]

And you have to initialize your function with 0 , which could be considered a bad pattern. You could write and use highest_value/1 which does that for you

highest_value(List) ->
   highest_value(List, 0).

Or even use a modification of this algorithm: since the largest number will be one of the numbers in the list, you could use the first element as the function initialization.

highest_value(_List = [First|T]) when First > 0 ->
   highest_value(T, First).

This assumes that handling negative numbers is something you don't care about right now.

While debugging via print statements is common and even sometimes useful, and io:format can be used for this purpose in Erlang as already noted , Erlang provides powerful built-in tracing capabilities you should use instead.

Let's say your highest_value/2 and divide/1 functions reside in a module named hv . First, we compile hv in the Erlang shell:

1> c(hv).
{ok,hv}

Next, we use Erlang's dbg module to enable tracing on the hv functions:

2> dbg:tracer().
{ok,<0.41.0>}
3> dbg:p(self(),call).
{ok,[{matched,nonode@nohost,26}]}
4> dbg:tpl(hv,c).
{ok,[{matched,nonode@nohost,5},{saved,c}]}

In command 2 we enable debug tracing and in command 3 we indicate that we want to trace function calls in our current process (returned by self() ). In command 4 we create a call trace, using the built-in c trace specification, on all functions in the hv module.

Once debug tracing is enabled, we call hv:divide/1 and the trace output begins:

5> hv:divide([4,8,12,16]).
(<0.34.0>) call hv:divide([4,8,12,16]) ({erl_eval,do_apply,6})
(<0.34.0>) call hv:'-divide/1-lc$^0/1-0-'([4,8,12,16],[4,8,12,16]) ({erl_eval,
                                                                     do_apply,
                                                                     6})
(<0.34.0>) call hv:highest_value([4,8,12,16],0) ({hv,'-divide/1-lc$^0/1-0-',2})
(<0.34.0>) call hv:highest_value([[8,12,16]],4) ({hv,'-divide/1-lc$^0/1-0-',2})
(<0.34.0>) call hv:highest_value([[]],[8,12,16]) ({hv,'-divide/1-lc$^0/1-0-',2})
(<0.34.0>) call hv:highest_value([[]],[8,12,16]) ({hv,'-divide/1-lc$^0/1-0-',2})
...

First, note that I abbreviated the trace output because at the ... point it's already in an infinite loop, and the remainder of the trace is identical to the two statements prior to the ... .

What does the trace output tell us? The first line shows the invocation of the divide/1 function, and the second line shows the call to the list comprehension inside divide/1 . We then see calls to highest_value/2 , first with the full list and N set to 0. The next call is where it gets interesting: because your code passes [T] rather than T as the first argument in the recursive invocation of highest_value/2 , H has the value [8,12,16] , which Erlang treats as being greater than the current N value of 4, so the next recursive call is:

highest_value([T], [8,12,16]).

and because T is [] , this turns into:

highest_value([[]], [8,12,16]).

Here, H is [] , and T is also [] . H is not greater than [8,12,16] , so all remaining recursive invocations after this point are identical to this one, and the recursion is infinite.

To fix this, you need to pass T correctly as already noted :

highest_value([H|T], N) when H > N, H > 0 ->
    highest_value(T, H);
highest_value([_|T], N) ->
    highest_value(T, N).

Then recompile, which also reloads your module, and because of that you'll also need to set up your debug tracing again:

5> c(hv).
{ok,hv}
6> dbg:tpl(hv,c).
{ok,[{matched,nonode@nohost,5},{saved,c}]}
7> hv:divide([4,8,12,16]).
(<0.34.0>) call hv:divide([4,8,12,16]) ({erl_eval,do_apply,6})
(<0.34.0>) call hv:'-divide/1-lc$^0/1-0-'([4,8,12,16],[4,8,12,16]) ({erl_eval,
                                                                     do_apply,
                                                                     6})
(<0.34.0>) call hv:highest_value([4,8,12,16],0) ({hv,'-divide/1-lc$^0/1-0-',2})
(<0.34.0>) call hv:highest_value([8,12,16],4) ({hv,'-divide/1-lc$^0/1-0-',2})
(<0.34.0>) call hv:highest_value([12,16],8) ({hv,'-divide/1-lc$^0/1-0-',2})
(<0.34.0>) call hv:highest_value([16],12) ({hv,'-divide/1-lc$^0/1-0-',2})
(<0.34.0>) call hv:highest_value([],16) ({hv,'-divide/1-lc$^0/1-0-',2})
** exception error: no true branch found when evaluating an if expression
     in function  hv:highest_value/2 (/tmp/hv.erl, line 5)
     in call from hv:'-divide/1-lc$^0/1-0-'/2 (/tmp/hv.erl, line 15)

Tracing now shows that highest_value/2 is working as expected, but we now hit a new problem with the if statement, and the fix for this is already explained in another answer so I won't repeat it here.

As you can see, Erlang's tracing is far more powerful than using "print debugging".

  • It can be turned on and off interactively in the Erlang shell as needed.
  • Unlike debugging in other languages, debug tracing requires no special compilation flags for your modules.
  • Unlike with debug print statements, you need not change your code and recompile repeatedly.

What I've shown here barely scratches the surface as far as Erlang's tracing capabilities go, but it was more than enough to find and fix the problems.

And finally, note that using the lists:max/1 standard library call you can more easily achieve what your module is trying to do:

divide(L) ->
    case lists:max(L) of
        N when N > 0 ->
            [V/N || V <- L];
        _ ->
            error(badarg, [L])
    end.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM