2016-04-24 16 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構造的建議也是一個好主意;爲了完整性,我只是添加這些方法。

相關問題