Return statement in Elixir

后端 未结 8 1380
野趣味
野趣味 2020-12-14 00:38

I need a function with some kind of a step-by-step logic and I wonder how I can make one. Let\'s take a log in process on a site as an example, so I need the following logic

相关标签:
8条回答
  • 2020-12-14 01:33

    This is exactly the situation I'd use elixir pipes library

    defmodule Module do
      use Phoenix.Controller
      use Pipe
    
      plug :action
    
      def action(conn, params) do
        start_val = {:ok, conn, params}
        pipe_matching {:ok, _, _},
          start_val
            |> email_present
            |> email_length
            |> do_action
      end
    
      defp do_action({_, conn, params}) do
        # do stuff with all input being valid
      end
    
      defp email_present({:ok, _conn, %{ "email" => _email }} = input) do
        input
      end
      defp email_present({:ok, conn, params}) do
        bad_request(conn, "email is a required field")
      end
    
      defp email_length({:ok, _conn, %{ "email" => email }} = input) do
        case String.length(email) > 5 do
          true -> input
          false -> bad_request(conn, "email field is too short")
      end
    
      defp bad_request(conn, msg) do
        conn 
          |> put_status(:bad_request) 
          |> json( %{ error: msg } )
      end
    end
    

    Note, this produces long pipes a lot of times and it is addictive :-)

    Pipes library has more ways to keep piping than pattern matching I used above. Have a look elixir-pipes at the examples and tests.

    Also, if validation becomes a common theme in your code maybe it is time to check Ecto's changeset validations or Vex another library that does nothing else but validate your input.

    0 讨论(0)
  • 2020-12-14 01:35

    What you're looking for is what I'd call an "early exit". I had the same question when I started with functional programming in F# quite a while ago. The answers I got for that may be instructive:

    Multiple Exits From F# Function

    This is also a good discussion of the question (although again it's F#):

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

    TL;DR construct your functions as a series of functions each taking and returning a tuple of an atom and the password string to check. The atom will either be :ok or :error. Like so:

    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
    

    And you would use it like so:

    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)> 
    

    Of course, you'll want to construct your own password tests but the general principle should still apply.


    EDIT: Zohaib Rauf did a very extensive blog post on just this idea. Well worth reading as well.

    0 讨论(0)
提交回复
热议问题