Как проверить, пустой ли список в Erlang?

В основном у меня есть структура, которая включает значение и список идентификаторов. Что я хочу сделать, так это сопоставить список идентификаторов и отправить им сообщение, но когда я впервые инициализирую список идентификаторов, я помещаю переменную «empty_set» (возможно, мне следует переименовать ее в empty_list: P).

Проблема в том, что всякий раз, когда я вызываю функцию карты, я хочу сначала проверить, является ли список "empty_set", а если нет, то использовать в нем функцию карты. Вот код:

{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, которое я должен проверить, пуст ли список, он все равно пытается сопоставить его.

Так что я делаю не так с выражением?

Спасибо


person sokras    schedule 02.11.2014    source источник


Ответы (4)


Сопоставление с образцом. Если вам нужно проверить пустой список в страже или 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...

Куда бы вы ни отправляли изнутри вашего приема, это то, что должно выполнять фактическую работу, и это также место, где вы хотите выполнять сопоставление. Поскольку это в любом случае вызов функции, сопоставление здесь хорошо подходит и упрощает ваш код.

Если вы хотите сопоставить в самом 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, который действительно упрощает создание конечных автоматов. (Их тоже легко написать вручную в Erlang, но еще проще с модулем gen_fsm.)

Попробуйте Регистр, чтобы проверить, когда список пуст а не рекурсия?

person zxq9    schedule 02.11.2014
comment
Итак, что вы в основном говорите, это сделать это? case ViewerSet of empty_list -> looper(V, ViewerSet); [_] -> set_viewer_values(V, ViewerSet) - person sokras; 02.11.2014
comment
Я не могу выполнить сопоставление рисунка с помощью петлителя. Могу я? - person sokras; 02.11.2014
comment
@sokras Возможности сопоставления с образцом существуют в Erlang повсюду, вам просто нужно попрактиковаться в их использовании. Я добавил два примера выше, один в set_viewer_values/2 и один в самом looper/2. - person zxq9; 03.11.2014

Что произойдет с обоими if выражениями, если ViewerSet равно empty_set? Нет охранника, который бы занимался этим делом.

if выражения в Erlang не являются типичными 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, либо как часть предложения функции.

person juan.facorro    schedule 02.11.2014

Если у вас есть список идентификаторов в переменной ViewerSet, просто инициализируйте его пустым списком: [].

Затем, когда вы получите сообщение {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);
...

or

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

Исходя из вашего кода, вы должны получить следующее:

(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> 

Так что, похоже, проблема в другом.

person zsoci    schedule 22.11.2014