如何在函数式编程语言中使用函数而不是早期返回

时间:2016-04-24 04:10:52

标签: functional-programming elixir

我最近开始学习Elixir,我真的非常喜欢它,但它是我用过的第一种函数式编程语言。我遇到的问题来自于我一直在阅读教程并在LearnElixir观看截屏视频,你应该尽量避免使用IF类型语句。

但我发现自己经常嵌套condcase

我会用Golang或Javascript之类的其他语言解决这些解决方案,只需使用带有早期返回的if语句,以便超出该范围的代码不会运行,这使我无法在99%的时间内通过只是检查虚假值并返回。

所以在Elixir(或其他函数式编程语言)中,如何在不使用嵌套的情况下以适当的方式编写类似下面的内容,并利用该语言的功能。

def loginPost(conn, %{"user" => user_params}) do
    # Look for user in database
    query = from u in User,
      where: u.email == ^user_params["email"],
      select: [u.email, u.username, u.password]

    data = Repo.all(query)

    # Check to see if a user had been found
    case (length data) == 0 do
      true -> # No user was found, send an error message
        conn
        |> json(%{ success: false, errors: ["Wrong username or password"]})
      false -> # A user was found, compare password
        [[email, username, db_password]] = data
        case Comeonin.Bcrypt.checkpw(user_params["password"], db_password) do
          true -> # Password was correct, set session and return a json response
            conn
            |> put_session(:authenticated, true)
            |> put_session(:username, username)
            |> put_session(:email, email)
            |> json(%{success: true}) # Send json response and redirect on client side
          false -> # Password was incorrect, send an error message
            conn
            |> json(%{success: false, errors: ["Wrong username or password"]})
        end
    end
  end
end

2 个答案:

答案 0 :(得分:7)

一种方法是使用with。您可以创建单独的函数,如下所示:

def authenticate(email, password) do
  with {:ok, user} <- find_user(email),
    {:ok, user} <- validate_password(user, password),
    {:ok, user} <- validate_preconditions(user)
  do: {:ok, user}
end

defp find_user(email) do
  # return {:ok, user} if user is found, return {:error, :user_not_found} otherwise
end

defp validate_password(user, password) do
  # return {:ok, user} if password is correct, return {:error, :invalid_password} otherwise
end

defp validate_preconditions(user) do
  # return {:ok, user} if user is not banned or whatever, return {:error, :cant_be_logged_in} otherwise
end

然后你可以在你的控制器功能中使用它:

def loginPost(conn, %{"user" => user_params}) do
  case authenticate(user_params["email"], user_params["password"]) do
    {:ok, user} -> # start session
    {:error, error_type} -> # handle error
  end
end

这个例子可能会更好,但你明白了。

您也可以阅读此question

的答案

答案 1 :(得分:0)

函数式编程的一个好处(和约束)是所有函数必须返回一个值。在类型FP(例如F#,Scala,Haskell)中,函数的所有可能出口的返回类型必须相同。因此,如果输入错误,我不能有一个返回 false 的函数,但如果输入正常,则返回一个数字。如何处理这种情况?

1。)使函数的返回类型成为某种元组。这是许多语言的常见做法。当事情正常时,Erlang有许多函数返回{:ok, value},而当问题出现时,{:error, message}(或类似的东西)会返回:ok。然后调用函数在使用元组中的第二个元素之前询问原子以确保它是def max(a, b) when is_number(a) and is_number(b) do 。这有点像黑客,但这不是世界上最糟糕的事情。

2。)你可以抛出异常。当然这似乎有点极端,如果它有意义,没有特别好的理由可以避免这种做法。

3。)您可以在输入上添加警戒,以确保您不会在第一时间获得错误的输入。

例如,考虑一下:

max

当然,这可以防止我意外地用一封信或其他任何数字来呼叫with。人们可以通过额外的警卫进一步限制投入。这将再次消除提前退出的原因之一。

我提供这些作为该问题的其他三种方法。我认为@JustMichael关于使用{{1}}结构的建议也是一个好主意;为了完整起见,我只是添加了这些方法。