13

So when a user sends a request to register an account, they send their username, password, email, and other info. The registration function must verify all of their data. An example would be:

  • verify email not in use
  • verify username not in use
  • verify username is alphanumeric
  • verify all fields are above X characters long
  • verify all fields are less than Y characters long

Now I don't want to have a 5 level deep if or case statement, but what other options do I have? Splitting it into separate functions sounds like a good idea, but then I just have to check the return value of the functions in some sort of conditional and it's back to the original problem.

I could separate them into functions and then call an if statement with all of the conditionals OR'd together, but that wouldn't give me what I want because I need to be able to tell the user the specific error if there was one.

How does one handle this kind of situation in erlang? Is there an equivalent of a return statement, or does it have to be the last executable line in a function to be a return value?

ryeguy
  • 65,519
  • 58
  • 198
  • 260

4 Answers4

35

One of Joe Armstrong's suggestion: program success case code separated from error handling. You can make it in this way

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.

note that you can do all errors catches out of create_user function which is better.

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.

Pattern match is one of coolest things in Erlang. Note that you can involve your tag to badmatch error

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

and custom data too

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

If exception is fast enough for you depends of your expectations. Speed on my 2.2GHz Core2 Duo Intel: about 2 millions exceptions in one second (0.47us) compared to 6 millions success (external) function calls (0.146us) - one can guess that exception handling takes about 0.32us. In native code it is 6.8 vs 47 millions per second and handling can take about 0.125us. There can be some additional cost for try-catch construct which is about 5-10% to success function call in both native and byte-code.

Hynek -Pichi- Vychodil
  • 26,174
  • 5
  • 52
  • 73
  • 3
    +1 for the answer: On both erlang books isn't' explicit that you can put more than one expression on try/catches. Ty for the info! – scooterman Mar 04 '10 at 18:21
  • does this require the functions like `new_email` to throw a specific type of error like `email_in_use`? – Tommy Nov 17 '16 at 19:55
  • @Tommy: No, just return: `try ok = (fun() -> email_in_use end)() catch error:{badmatch, email_in_use} -> io:format("OK!~n", []) end.` – Hynek -Pichi- Vychodil Nov 18 '16 at 09:37
4
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

So it isn't 5 level deep any more. For real program i guess you should use lists:foldl for accumulate error message from every checking function. Because for now it simple says 'all fine' or 'some problem'.

Note that in this way add or remove checking condition isn't a big deal

And for "Is there an equivalent of a return statement..." - look at try catch throw statement, throw acts like return in this case.

JLarky
  • 9,833
  • 5
  • 36
  • 37
0

Building up on @JLarky's answer, here's something that i came up with. It also draws some inspiration from Haskell's monads.

-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
]).
sa___
  • 363
  • 2
  • 12
-3

Maybe you will need using of

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

But, of course, there will be spawn() methods.

user2342224
  • 39
  • 1
  • 3