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