Как я могу элегантно проверить многие условия в Эрланге?
Поэтому, когда пользователь отправляет запрос на регистрацию учетной записи, они отправляют свое имя пользователя, пароль, электронную почту и другую информацию. Функция регистрации должна проверять все свои данные. Примером может служить:
- проверить, что адрес электронной почты не используется
- проверить имя пользователя не используется
- проверить имя пользователя - буквенно-цифровое
- проверить, что все поля превышают 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().