Elixir的退货声明

NoD*_*ame 30 elixir phoenix-framework

我需要一个具有某种逐步逻辑的函数,我想知道如何制作一个.我们以站点上的登录过程为例,所以我需要以下逻辑:

1)电子邮件存在?是的 - >继续; 否 - >返回错误

2)电子邮件至少有5个字符?是的 - >继续; 否 - >返回错误

3)密码存在?是的 - >继续; 否 - 返回错误

等等 ...

为了实现这一点,我通常会使用一个return语句,以便如果电子邮件不存在,我退出执行该函数并使其返回错误.但是我在Elixir找不到类似的东西,所以我需要一个建议.我现在能看到的唯一方法是使用嵌套条件,但也许有更好的方法?

sas*_*ric 28

这是一个有趣的问题,因为您需要执行多个检查,提前退出,并在此过程中转换某些状态(连接).我通常按​​如下方式处理此问题:

  • 我将每个检查实现为一个函数,它将state输入作为输入并返回{:ok, new_state}{:error, reason}.
  • 然后,我构建一个通用函数,它将调用一个检查函数列表,并返回第一个遇到的{:error, reason}或者{:ok, last_returned_state}所有检查都成功.

让我们先看看泛型函数:

defp perform_checks(state, []), do: {:ok, state}
defp perform_checks(state, [check_fun | remaining_checks]) do
  case check_fun.(state) do
    {:ok, new_state} -> perform_checks(new_state, remaining_checks)
    {:error, _} = error -> error
  end
end
Run Code Online (Sandbox Code Playgroud)

现在,我们可以使用它如下:

perform_checks(conn, [
  # validate mail presence
  fn(conn) -> if (...), do: {:error, "Invalid mail"}, else: {:ok, new_conn} end,

  # validate mail format
  fn(conn) -> if (...), do: {:error, "Invalid mail"}, else: {:ok, new_conn} end,

  ...
])
|> case do
  {:ok, state} -> do_something_with_state(...)
  {:error, reason} -> do_something_with_error(...)
end
Run Code Online (Sandbox Code Playgroud)

或者将所有检查移动到命名的私有函数,然后执行:

perform_checks(conn, [
  &check_mail_presence/1,
  &check_mail_format/1,
  ...
])
Run Code Online (Sandbox Code Playgroud)

您还可以查看elixir-pipes,它可能会帮助您用管道表达它.

最后,在Phoenix/Plug的上下文中,您可以将检查声明为一系列插件并在第一次出错时停止.


ewH*_*ewH 18

我知道这个问题很老,但我遇到了同样的情况,发现从Elixir 1.2开始,你也可以使用with使你的代码非常易读的语句.do:如果所有子句匹配,则执行该块,否则将暂停该块,并返回不匹配的值.

defmodule MyApp.UserController do
  use MyApp.Web, :controller

  def create(conn, params) do
    valid = 
      with {:ok} <- email_present?(params["email"]),
        {:ok} <- email_proper_length?(params["email"),
        {:ok} <- password_present?(params["password"]),
      do: {:ok} #or just do stuff here directly

    case valid do
      {:ok} -> do stuff and render ok response
      {:error, error} -> render error response
    end
  end

  defp email_present?(email) do
    case email do
      nil -> {:error, "Email is required"}
      _ -> {:ok}
    end
  end

  defp email_proper_length?(email) do
    cond do
      String.length(email) >= 5 -> {:ok}
      true -> {:error, "Email must be at least 5 characters"}
    end
  end

  defp password_present?(password) do
    case email do
      nil -> {:error, "Password is required"}
      _ -> {:ok}
    end
  end
end
Run Code Online (Sandbox Code Playgroud)


Ono*_*cci 6

您正在寻找的是我称之为"提前退出"的内容.很久以前,当我开始使用F#中的函数式编程时,我遇到了同样的问题.我得到的答案可能是有益的:

F#函数多次退出

这也是对这个问题的一个很好的讨论(尽管它也是F#):

http://fsharpforfunandprofit.com/posts/recipe-part2/

TL; DR将您的函数构造为一系列函数,每个函数获取并返回一个原子元组和要检查的密码字符串.原子将是:ok或:error.像这样:

defmodule Password do

  defp password_long_enough?({:ok = a, p}) do
    if(String.length(p) > 6) do
      {:ok, p}
    else
      {:error,p}
    end
  end

  defp starts_with_letter?({:ok = a, p}) do
   if(String.printable?(String.first(p))) do
     {:ok, p}
   else
     {:error,p}
   end      
  end


  def password_valid?(p) do
    {:ok, _} = password_long_enough?({:ok,p}) |> starts_with_letter?
  end

end
Run Code Online (Sandbox Code Playgroud)

你会像这样使用它:

iex(7)> Password.password_valid?("ties")
** (FunctionClauseError) no function clause matching in Password.starts_with_letter?/1
    so_test.exs:11: Password.starts_with_letter?({:error, "ties"})
    so_test.exs:21: Password.password_valid?/1
iex(7)> Password.password_valid?("tiesandsixletters")
{:ok, "tiesandsixletters"}
iex(8)> Password.password_valid?("\x{0000}abcdefg")
** (MatchError) no match of right hand side value: {:error, <<0, 97, 98, 99, 100, 101, 102, 103>>}
    so_test.exs:21: Password.password_valid?/1
iex(8)> 
Run Code Online (Sandbox Code Playgroud)

当然,您需要构建自己的密码测试,但一般原则仍然适用.


编辑:Zohaib Rauf 就这个想法做了一篇非常广泛的博客文章.非常值得一读.