2016-04-24 37 views
2

我最近开始学习Elixir,我非常享受它,但它是我用过的第一个函数式编程语言。我面临的问题来自我一直在阅读的教程,并且在LearnElixir上观看屏幕录像是您应该尽量避免使用IF类型的陈述。如何在函数式编程语言中使用函数而不是早期返回

但我发现自己经常筑巢condcase

我会解决像Golang或JavaScript等语言这些解决方案通过只是使用if statment以早日回归,让超越该代码将无法运行,这阻止了我99%的时间必须通过检查falsy值并返回来嵌套条件。

因此,在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 
+1

我对如果应该避免的原因更加好奇......它根本没有意义。在任何语言中,分支都是程序的基石。在一个非平凡的程序中必然会出现某种分支。 – HuStmpHrrr

+0

@HuStmpHrrr我在Elixir的IRC/Slack上讨论过的一些人通常会这样说,如果我太嵌套了,我想我在一个函数中做了太多的事情,并且会写另一个 – Datsik

+0

这是别的东西。它适用于所有语言,即避免过多的嵌套结构。在fp中,没有循环结构,所以批评者都指向分支。我明白了这一点。它更多的是模块化。不仅仅是出于某种原因避免了某些特定的结构。如果使用if语句,我认为这段代码会更好。 – HuStmpHrrr

回答

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

0

一个功能编程的好处(和约束)阅读的答案是,所有的功能必须返回一个值。在类型化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构造的建议也是一个好主意;为了完整性,我只是添加这些方法。

相关问题