【问题标题】:How do I elegantly check many conditions in Erlang?如何优雅地检查 Erlang 中的许多条件?
【发布时间】:2010-10-14 13:06:14
【问题描述】:

因此,当用户发送注册帐户的请求时,他们会发送用户名、密码、电子邮件和其他信息。注册功能必须验证他们的所有数据。一个例子是:

  • 验证未使用的电子邮件
  • 验证用户名未使用
  • 验证用户名是字母数字
  • 验证所有字段的长度都超过 X 个字符
  • 验证所有字段的长度小于 Y 个字符

现在我不想使用 5 级深度的 if 或 case 语句,但我还有什么其他选择?将其拆分为单独的函数听起来是个好主意,但是我只需要在某种条件下检查函数的返回值,它就会回到最初的问题。

我可以将它们分成函数,然后调用一个带有所有条件 OR 的 if 语句,但这不会给我我想要的,因为我需要能够告诉用户特定的错误,如果有是一个。

如何在 erlang 中处理这种情况?是否有等效的 return 语句,或者它必须是函数中的最后一个可执行行才能成为返回值?

【问题讨论】:

  • Erlang 邮件列表的交叉帖子 - 现在有相当数量的交叉帖子...

标签: erlang


【解决方案1】:
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 级深了。对于真正的程序,我猜你应该使用 lists:foldl 从每个检查函数中累积错误消息。因为现在它简单地说“一切都好”或“有些问题”。

请注意,这种方式添加或删除检查条件并不是什么大问题

对于“是否有等效的 return 语句...” - 查看 try catch throw 语句,在这种情况下 throw 的行为类似于 return。

【讨论】:

    【解决方案2】:

    Joe Armstrong 的建议之一:将程序成功案例代码与错误处理分开。你可以这样做

    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.
    

    模式匹配是 Erlang 中最酷的事情之一。请注意,您可以将您的标签包含在错误匹配错误中

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

    还有自定义数据

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

    异常是否足够快取决于您的期望。我的 2.2GHz Core2 Duo Intel 的速度: 一秒钟内大约 200 万个异常(0.47us)与 600 万个成功(外部)函数调用(0.146us)相比——可以猜测异常处理大约需要 0.32us。 在本机代码中,它是每秒 6.8 对 4700 万,处理可能需要大约 0.125us。 try-catch 构造可能会产生一些额外的成本,在本机和字节码中成功调用函数的成本约为 5-10%。

    【讨论】:

    • +1 答案:在两本 erlang 书籍中都没有明确表示您可以在 try/catch 上放置多个表达式。 Ty 获取信息!
    • 这是否需要像new_email 这样的函数来抛出像email_in_use 这样的特定类型的错误?
    • @Tommy:不,只需返回:try ok = (fun() -> email_in_use end)() catch error:{badmatch, email_in_use} -> io:format("OK!~n", []) end.
    【解决方案3】:

    也许你需要使用of

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

    但是,当然会有 spawn() 方法。

    【讨论】:

      【解决方案4】:

      在@JLarky 的回答的基础上,这是我想出的一些东西。它还从 Haskell 的单子中汲取了一些灵感。

      -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
      ]).
      

      【讨论】:

        猜你喜欢
        • 2019-03-05
        • 2019-12-12
        • 1970-01-01
        • 1970-01-01
        • 2012-05-08
        • 2013-04-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多