虽然通过打印语句进行调试很常见,有时甚至很有用,并且 io:format 可以在 Erlang as already noted 中用于此目的,但 Erlang 提供了强大的内置跟踪功能,您应该使用它。
假设您的highest_value/2 和divide/1 函数驻留在名为hv 的模块中。首先,我们在 Erlang shell 中编译 hv:
1> c(hv).
{ok,hv}
接下来,我们使用Erlang's dbg module 来启用对hv 函数的跟踪:
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}]}
在命令 2 中,我们启用调试跟踪,在命令 3 中,我们指示我们要跟踪当前进程中的函数调用(由 self() 返回)。在命令 4 中,我们使用内置的 c 跟踪规范在 hv 模块中的所有函数上创建调用跟踪。
启用调试跟踪后,我们调用hv:divide/1 并开始跟踪输出:
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})
...
首先,请注意我简化了跟踪输出,因为在... 点它已经处于无限循环中,并且跟踪的其余部分与... 之前的两个语句相同。
跟踪输出告诉我们什么?第一行显示了对divide/1 函数的调用,第二行显示了对divide/1 中的列表推导的调用。然后我们看到对 highest_value/2 的调用,首先是完整列表,N 设置为 0。下一个调用是有趣的地方:因为您的代码传递 [T] 而不是 T 作为递归中的第一个参数调用highest_value/2,H的值是[8,12,16],Erlang认为它大于当前N的值4,所以下一个递归调用是:
highest_value([T], [8,12,16]).
因为T 是[],所以变成:
highest_value([[]], [8,12,16]).
这里,H 是 [],T 也是 []。 H 不大于[8,12,16],所以此时所有剩余的递归调用都与此相同,递归是无限的。
要解决这个问题,您需要正确传递T 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).
然后重新编译,这也会重新加载您的模块,因此您还需要再次设置调试跟踪:
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)
现在跟踪显示highest_value/2 正在按预期工作,但是我们现在遇到了if 语句的新问题,并且已经解释了解决此问题的方法in another answer,所以我不会在这里重复。
如您所见,Erlang 的跟踪功能远比使用“打印调试”强大。
- 可以根据需要在 Erlang shell 中以交互方式打开和关闭它。
- 与其他语言的调试不同,调试跟踪不需要为您的模块设置特殊的编译标志。
- 与调试打印语句不同,您无需更改代码并重复重新编译。
就 Erlang 的跟踪功能而言,我在这里展示的内容几乎没有触及表面,但对于发现和解决问题已经绰绰有余了。
最后,请注意,使用 lists:max/1 标准库调用可以更轻松地实现模块尝试做的事情:
divide(L) ->
case lists:max(L) of
N when N > 0 ->
[V/N || V <- L];
_ ->
error(badarg, [L])
end.