【问题标题】:How do I check where my code gets stuck in Erlang?如何检查我的代码在 Erlang 中的卡住位置?
【发布时间】:2014-12-14 15:20:14
【问题描述】:

我正在尝试编写一个函数,它接收一个列表,找到列表中的最大值整数,然后将列表中的所有其他整数除以该值。

不幸的是,我的代码卡在了某个地方。例如,如果这是 python,我可以轻松地编写几个不同的“打印”并查看它卡在哪里。但是你如何在 Erlang 中做到这一点?

这里是代码。

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

【问题讨论】:

  • 请注意,您的 divide/1 函数会低效地为列表理解中处理的每个元素重新计算列表中的最大值。您应该首先调用highest_value(_L, 0),将其返回值绑定到一个变量,并将该变量用作列表推导中的除数。

标签: debugging functional-programming erlang pattern-matching


【解决方案1】:

虽然通过打印语句进行调试很常见,有时甚至很有用,并且 io:format 可以在 Erlang as already noted 中用于此目的,但 Erlang 提供了强大的内置跟踪功能,您应该使用它。

假设您的highest_value/2divide/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/2H的值是[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.

【讨论】:

    【解决方案2】:

    对于打印,您可以使用io:format/2。一样的。

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

    编辑

    您弄错的一件事是[H | T] 语法。 H 或 head 是列表中的第一个元素。 T 代表尾部,或“列表的其余部分”。顾名思义,tail 是一个列表(可能是一个空列表,但仍然是一个列表)。因此,当您进行递归时,您不需要将 T 放入新列表中。

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

    在您调用的旧代码中:

       highest_value([T], N).
    

    它创建了一个包含一个元素的新列表,例如[[2,3,4,5]]。如果你对这个进行头尾,你会得到这个唯一元素列表作为头部,而一个空列表作为尾部。


    此外,在您的第一个函数子句中,您有一个原子 'Error! No positive values.'(单引号表示这只是一个长原子,而不是字符串),它永远不会返回(您将始终返回 N)。如果你想返回一些原子或N,取决于N 的值,你可以扩展你对函数子句的使用

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

    你必须用0 初始化你的函数,这可能被认为是一个糟糕的模式。您可以编写和使用highest_value/1,它会为您做到这一点

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

    或者甚至使用这个算法的修改:因为最大的数字将是列表中的一个数字,你可以使用第一个元素作为函数初始化。

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

    这假设处理负数是你现在不关心的事情。

    【讨论】:

    • 我对代码做了一些更改,我无法应用您提到的所有内容。但这是一个更新的代码版本。它仍然挂起。 highest_value([], N) when N =&lt; 0 -&gt; 'Error! No positive values.'; highest_value([], N) when N &gt; 0 -&gt; N; highest_value([H|T], N) when H &gt; N -&gt; highest_value([T], H); highest_value([_|T], N) -&gt; highest_value([T], N). normalize(_L) -&gt; [X / highest_value(_L, 0) || X &lt;- _L].@mpm 我认为当只剩下一项要检查时它会冻结。但我不知道如何解决这个问题:(。
    • 仍然有同样的错误,我仍然相信你可以通过io:format'ing 找到它。如果没有,请再次 ping 我。
    • 好吧,似乎列表在迭代后没有拆分列表。我真的不知道如何解决这个问题。 &gt;&gt;&gt; when H bigger than N &gt;&gt;&gt; H: 1, T: [2,3,4,5], N: 0 &gt;&gt;&gt; when H bigger than N &gt;&gt;&gt; H: [2,3,4,5], T: [], N: 1 @mpm
    • 类似的东西;我已经更新了我的答案,几乎没有解释。
    • 当您递归调用highest_value 时,只需不要T 包装到列表中。不是highest_value([T], N),而是highest_value(T, N)。这在两个调用中都是必需的。
    猜你喜欢
    • 1970-01-01
    • 2016-11-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-09-07
    • 2013-09-02
    • 1970-01-01
    相关资源
    最近更新 更多