如何在Erlang中检查列表是否为空?

ruarlubt  于 2022-12-16  发布在  Erlang
关注(0)|答案(4)|浏览(258)

基本上我有一个结构,包括一个值和一个ID列表.我想做的是Map到ID列表,并发送消息给他们,但当我第一次初始化ID列表时,我把变量“empty_set”.(也许我应该重命名为empty_list:P).
问题是,每当我调用map函数时,我想先检查列表是否为“empty_set”,如果不是,就在列表中使用map函数。

{From, set_value, V} ->
  if ViewerSet /= empty_set -> set_viewer_values(V, ViewerSet)
  end,
looper(V, ViewerSet)

以下是调用的函数:

set_viewer_values(Value, ViewerSet) ->
  if ViewerSet /= empty_set ->
    lists:map(fun(ViewerPid) ->
        ViewerPid ! {self(), set_value, Value} end, ViewerSet)
  end.

我是这样开始这个过程的:

process() ->
  C = spawn(fun() -> looper(no_value, empty_set) end),
  {ok, C}.

问题是,当我运行它,我得到这个错误:

=ERROR REPORT==== 2-Nov-2014::15:03:07 ===
Error in process <0.367.0> with exit value: {function_clause,[{lists,map,
[#Fun<sheet.2.12938396>,empty_set],[{file,"lists.erl"},{line,1223}]},{lists,map,2,
[{file,"lists.erl"},{line,1224}]},{sheet,cell_loop,2,[{file,"sheet.erl"},{line,93}]}]}

据我所知,尽管我必须使用if表达式来检查列表是否为空,但它仍然试图Map列表。
那么,我的表达有什么不对吗?
谢谢

jbose2ul

jbose2ul1#

模式匹配:如果你需要检查guard或者if或者cond中的空列表,那么几乎可以肯定你对Erlang的思考方式存在结构性问题。
这几乎总是表现在令人困惑的代码和奇怪的边缘情况中,让你问自己“我如何检查一个空列表?”而没有意识到你真正在问的是“我如何检查一个空列表 * 作为一个过程条件 *?”这是健全的函数式编程的祸根。

编辑:可能需要更多说明和示例

无论你想在哪里注入模式匹配,你都可以使用像case这样的东西,或者你可以把你正在做的事情分解成一个单独的函数。你经常会发现,一方面,你有一个语义模糊的地方,事情耦合得太紧密了(您正在receive中执行除接收消息以外的工作),另一个则过于松散(在调用函数之前,您要进行大量的任意过程检查,而实际上参数匹配是自然的解决方案)。

looper(V, ViewerSet) ->
  receive
    {From, set_value, V} ->
        set_viewer_values(V, ViewerSet),
        looper(V, ViewerSet);
%   OtherStuff ->
%       whatever else looper/2 does...
  end.

set_viewer_values(V, []) ->
    set_default_values(V);
set_viewer_values(V, ViewerSet) ->
    % ... whatever the normal function definition is...

无论你从receive中将数据发送到哪里,都是应该做实际工作的地方,也是你想要做匹配的地方,因为这是一个函数调用,无论如何匹配在这里是一个很好的选择,并且简化了你的代码。
如果你想在looper/2中进行匹配,这当然是可能的。我不知道当你收到一个空列表时你想做什么,所以我会做一些事情,但是你可以做任何你想做的事情:

looper(V, []) ->
    looper(V, default_set());
looper(V, ViewerSet) ->
    % As before, or whatever makes sense.

你甚至可以决定,当你有一个空集时,你需要用一种完全不同的方式来操作:

full_looper(V, []) ->
    empty_looper(V);
full_looper(V, ViewerSet) ->
  receive
    {new_set, Set} ->
        looper(V, Set);
    {From, set_value, V} ->
        set_viewer_values(V, ViewerSet),
        looper(V, ViewerSet)
  end.

empty_looper(V) ->
  receive
    {new_set, Set} ->
        full_looper(V, Set);
    {From, set_value, V} ->
        set_viewer_values(V, default_set()),
        empty_looper(V)
  end.

我在上面的观点是,有许多方法可以处理空集的情况,而无需诉诸任意的过程性检查,并且一旦您了解了方法,所有这些方法都更容易阅读(不过,在你习惯这样做之前,这会感觉相当奇怪)。作为一个侧记,最后一个例子实际上是创建一个有限状态机--已经有一个OTP模块可以使创建FSM变得非常容易。(用Erlang手工编写它们也很容易,但使用gen_fsm模块更容易。)
当列表为空时,尝试Case检查而不是递归检查?

dwbf0jvd

dwbf0jvd2#

在这两个if表达式中,如果ViewerSetempty_set,会发生什么?没有处理这种情况的保护。
Erlang中的if表达式并不是其他语言中常见的if表达式,从我的经验来看,大多数情况下都会避免使用它们,这是有原因的:(作为已经提到的另一个答案)模式匹配可以用于检查相等性和其他比较操作(通过保护)。
以下内容摘自此处:
如果没有保护序列为真,则会发生if_clause运行时错误。如果需要,可以在最后一个分支中使用保护表达式true,因为该保护序列始终为真。
示例:

is_greater_than(X, Y) ->
    if
        X>Y ->
            true;
        true -> % works as an 'else' branch
            false
    end

所以if表达式最终是case的一种,但是用布尔值作为子句,它们往往会引入更多的混乱而不是清晰,有些人甚至避免使用if表达式。
我的建议是,每当您看到自己在使用if表达式时,问问自己如何用模式匹配替换它,或者用case替换,或者作为函数子句的一部分。

wfveoks0

wfveoks03#

如果变量ViewerSet中有一个id列表,只需使用空列表初始化它:[] .
然后,当你收到消息{From,set_value,V}时,你可以使用lists:foreach/2或列表解析为列表的每个元素执行一个函数(即使它是空的):

{From, set_value, V} ->
  lists:foreach(fun(ViewerPid) -> ViewerPid ! {self(), set_value, Value} end, ViewerSet),
  looper(V, ViewerSet);
...

{From, set_value, V} ->
  [fun(ViewerPid) -> ViewerPid ! {self(), set_value, Value} end || ViewerPid <- ViewerSet],
  looper(V, ViewerSet);
...
wb1gzix0

wb1gzix04#

根据您的代码,您应该得到以下内容:

(shell@a)8> Val.
myatom
(shell@a)9> if Val /= myatom -> lists:map(fun(X) -> io:format("~p",[X]) end, Val) end.
** exception error: no true branch found when evaluating an if expression
(shell@a)10>

看来问题出在别的地方。

相关问题