View Source Without (Without v0.1.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.

Summary

Types

error()

@type error() :: {:error, any()}

map_func()

@type map_func() :: (-> result()) | (any() -> result()) | (any(), map() -> result())

ok()

@type ok() :: {:ok, any()}

options()

@type options() :: [{:assign, atom()}]

result()

@type result() :: error() | ok()

t()

@type t() :: %Without{assigns: map(), result: :ok | :error, value: any()}

Functions

finit(value)

@spec finit(ok() | error() | any()) :: t()

fmap_error(context, func)

@spec fmap_error(t(), map_func()) :: t()

fmap_ok(context, func, opts \\ [])

@spec fmap_ok(any() | t(), map_func(), options()) :: t()

fresult(without)

@spec fresult(t()) :: {:ok, any()} | {:error, any()}