Как я могу элегантно проверить многие условия в Эрланге?

Поэтому, когда пользователь отправляет запрос на регистрацию учетной записи, они отправляют свое имя пользователя, пароль, электронную почту и другую информацию. Функция регистрации должна проверять все свои данные. Примером может служить:

  • проверить, что адрес электронной почты не используется
  • проверить имя пользователя не используется
  • проверить имя пользователя - буквенно-цифровое
  • проверить, что все поля превышают X символов
  • проверить, что все поля меньше Y символов длиной

Теперь я не хочу иметь 5-уровневую версию if или case, но какие у меня есть другие варианты? Разделение его на отдельные функции звучит как хорошая идея, но тогда мне просто нужно проверить возвращаемое значение функций в каком-то условном выражении и вернуться к исходной проблеме.

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

Как можно справиться с такой ситуацией в erlang? Есть ли эквивалент оператора return или он должен быть последней исполняемой строкой в ​​функции, которая является возвращаемым значением?

Ответы

Ответ 1

Одно из предложений Джо Армстронга: код случая успеха программы, отделенный от обработки ошибок. Вы можете сделать это таким образом.

create_user(Email, UserName, Password) ->
  try
    ok = new_email(Email),
    ok = valid_user_name(UserName),
    ok = new_user(UserName),
    ok = strong_password(Password),
    ...
    _create_user(Email, UserName, Password)
  catch
    error:{badmatch, email_in_use} -> do_something();
    error:{badmatch, invalid_user_name} -> do_something();
    error:{badmatch, user_exists} -> do_something();
    error:{badmatch, weak_password} -> do_something();
    ...
  end.

обратите внимание, что вы можете делать все ошибки, которые лучше всего улавливают функцию create_user.

create_user(Email, UserName, Password) ->
    ok = new_email(Email),
    ok = valid_user_name(UserName),
    ok = new_user(UserName),
    ok = strong_password(Password),
    ...
    _create_user(Email, UserName, Password).

main() ->
  try
    ...
    some_function_where_create_user_is_called(),
    ...
  catch
    ...
    error:{badmatch, email_in_use} -> do_something();
    error:{badmatch, invalid_user_name} -> do_something();
    error:{badmatch, user_exists} -> do_something();
    error:{badmatch, weak_password} -> do_something();
    ...
  end.

Сравнение с образцом - одна из самых крутых вещей в Эрланге. Обратите внимание, что вы можете связать тэг с ошибкой badmatch

{my_tag, ok} = {my_tag, my_call(X)}

и пользовательские данные тоже

{my_tag, ok, X} = {my_tag, my_call(X), X}

Если исключение достаточно быстро, зависит от ваших ожиданий. Скорость на моем 2,2 ГГц Core2 Duo Intel: около 2 миллионов исключений за одну секунду (0,47us) по сравнению с 6 миллионами успешных (внешних) вызовов функций (0.146us) - можно предположить, что обработка исключений занимает около 0,32us. В собственном коде это 6,8 против 47 миллионов в секунду, и обработка может занимать около 0.125us. Для конструкции try-catch может быть добавлена ​​дополнительная стоимость, которая составляет около 5-10% для успешного вызова функции как в собственном, так и в байтовом коде.

Ответ 2

User = get_user(),

Check_email=fun(User) -> not is_valid_email(User#user.email) end,
Check_username=fun(User) -> is_invalid_username(User#user.name) end,

case lists:any(fun(Checking_function) -> Checking_function(User) end, 
[Check_email, Check_username, ... ]) of
 true -> % we have problem in some field
   do_panic();
 false -> % every check was fine
   do_action()
 end

Итак, это уже не 5 уровней. Для реальной программы, я думаю, вы должны использовать списки: foldl для накопления сообщения об ошибке из каждой функции проверки. Потому что на данный момент это просто говорит "все хорошо" или "какая-то проблема".

Обратите внимание, что таким образом добавить или удалить условие проверки не имеет большого значения

И для "Существует ли эквивалент оператора возврата..." - посмотрите на команду try catch throw, в этом случае выполните действия типа return.

Ответ 3

Основываясь на ответе @JLarky, я кое-что придумал. Это также черпает вдохновение из монад Хаскелла.

-record(user,
    {name :: binary(), 
     email :: binary(), 
     password :: binary()}
).
-type user() :: #user{}.
-type bind_res() :: {ok, term()} | {error, term()} | term().
-type bind_fun() :: fun((term()) -> bind_res()).


-spec validate(term(), [bind_fun()]) -> bind_res().
validate(Init, Functions) ->
    lists:foldl(fun '|>'/2, Init, Functions).

-spec '|>'(bind_fun(), bind_res())-> bind_res().
'|>'(F, {ok, Result}) -> F(Result);
'|>'(F, {error, What} = Error) -> Error;
'|>'(F, Result) -> F(Result).

-spec validate_email(user()) -> {ok, user()} | {error, term()}. 
validate_email(#user{email = Email}) ->
...
-spec validate_username(user()) -> {ok, user()} | {error, term()}.
validate_username(#user{name = Name}) ->
...
-spec validate_password(user()) -> {ok, user()} | {error, term()}.    
validate_password(#user{password = Password}) ->
...

validate(#user{...}, [
    fun validate_email/1,
    fun validate_username/1,
    fun validate_password/1
]).

Ответ 4

Возможно, вам понадобится использовать

receive
    message1 -> code1;
    message2 -> code2;
    ...
end.

Но, конечно, будут методы spawn().