【问题标题】:How to use functions in a functional programming language instead of early returns如何在函数式编程语言中使用函数而不是提前返回
【发布时间】:2016-08-17 13:58:27
【问题描述】:

我最近开始学习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

【问题讨论】:

  • 我更好奇为什么应该避免 if 的原因......这根本没有意义。在任何语言中,分支都是程序的构建块。在一个重要的程序中一定有一些分支发生。
  • @HuStmpHrrr 我在 Elixir 的 IRC / Slack 上与我交谈过的一些人通常会说,哦,如果我太嵌套了,我想我在一个函数中做了太多事情,只会再写一个
  • 那是另一回事。它适用于所有语言,即避免过于嵌套的结构。在 fp 中,没有循环结构,所以批评者都会指向分支。我明白了。它更多地是关于模块化。不仅仅是出于某种原因避免某些特定的结构。我认为如果使用if,这段代码会更好。
  • @HuStmpHrrr By Code 你的意思是上面给出的例子吗?
  • 是的。布尔值的模式匹配很奇怪。但匹配一组布尔值是一种常见(也是很好)的做法。

标签: functional-programming elixir


【解决方案1】:

一种方法是使用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阅读答案

【讨论】:

    【解决方案2】:

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

    1.) 使函数的返回类型成为某种元组。这是许多语言中的常见做法。 Erlang 有很多函数,当事情正常时返回{:ok, value},当出现问题时返回{:error, message}(或类似的东西)。然后调用函数在使用元组中的第二个元素之前询问原子以确保它是:ok。这有点骇人听闻,但这并不是世界上最糟糕的事情。

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

    3.) 您可以在输入上添加保护措施,以确保您一开始就不会收到错误的输入。

    例如,考虑一下:

    def max(a, b) when is_number(a) and is_number(b) do
    

    当然,这可以防止我不小心用字母或除数字以外的任何东西打电话给max。可以通过额外的防护进一步限制输入。这将再次消除提前退出的原因之一。

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-04-27
      • 2010-10-25
      • 2011-04-01
      • 2019-06-04
      • 2017-01-28
      • 2017-03-10
      • 2017-04-26
      • 1970-01-01
      相关资源
      最近更新 更多