View Source Without (Without v0.2.0)
Without
is a tiny module to ease the usage of result tuple.
Using case
once is convenient, using it more than twice, makes the code less readable, consider this snippet
case find_user(email) do
{:ok, user} ->
case find_friends(user) do
{:ok, friends} -> "#{user} is friends of #{Enum.join(",", friends)}"
{:error, :friends_not_found} -> "#{user} doesn't have any friends"
end
{:error, :user_not_found} -> "user not found"
end
You might ask maybe with
could help on that?
with {:ok, user} <- find_user(email),
{:ok, friends} <- find_friends(user) do
"#{user} is friends of #{Enum.join(",", friends)}"
else
{:error, :user_not_found} -> "user not found"
{:error, :friends_not_found} -> "#{user} doesn't have any friends"
end
But the issue here is that variable user
is not available in the last else case!
Now that you feel the pain, let me introduce you to Without
!
email
|> Without.fmap_ok(&find_user/1, assign: :user)
|> Without.fmap_ok(&find_friends/1)
|> Without.fmap_error(fn error, assigns ->
case error do
:user_not_found -> "user not found"
:friends_not_found, assigns -> "#{assigns[:user]} doesn't have any friends"
end)
|> Without.fresult
If you are a functional programming aficionado, it might resemble monadic error handling.
One more thing! Without
is a lazy operator. It means your pipeline won't be executed until Without.fresult/1
is called.
It gives you the flexibility to build your pipeline by passing it around different module/functions and execute them on the last step.
WORD OF CAUTION: Use the following snippet with cautious, I haven't used this technique and might make your code complex and unreadable
def fetch_user(user, %Without{} = without) do
without
# Fetch it from somewhere if only previous steps are :ok!
|> Without.fmap_ok(fn -> {:ok, user} end, assign: :user)
end
def fetch_friends(%Without{} = without) do
without
# Fetch it from somewhere if only previous steps are :ok!
|> Without.fmap_ok(
fn _, assigns -> {:ok, ["#{assigns[:user]}-f01", "#{assigns[:user]}-f01"]} end,
assign: :friends
)
end
def render(%Without{} = without) do
without
|> Without.fmap_ok(fn friends ->
conn = render(conn, friends: friends)
{:ok, conn}
end)
|> Without.fmap_error(fn error ->
Logger.error("failed to make external calls due to #{error}")
conn = render(conn, error: error)
{:ok, conn}
end)
end
def index(conn, _) do
required_external_calls = Without.finit(nil)
# do other things.....
required_external_calls = fetch_user("milad", required_external_calls)
# do something else ...
required_external_calls = fetch_friends(required_external_calls)
{:ok, conn} =
required_external_calls
|> render()
|> Without.fresult()
conn
end