Err (Err v0.2.2)
View SourceErr is a tiny library that makes working with tagged {:ok, value} and {:error, reason} tagged tuples more ergonomic and expressive in Elixir.
It follows a simple design to permit using it in existing codebases without changing existing code:
- Tuples
{:ok, _}(of any size) are considered a success result. - Tuples
{:error, _}(of any size) are considered an error result. nilis considered "none".- Any other value is considered "some" value
Inspired by Rust's Result/Option and Gleam's result/option.
Features
- â Composable - Chain operations with
map,and_then,or_else - ð Drop-in compatibility - Handles existing tagged tuples
:ok/:errorof any size andnilvalues. No need to introduce%Result{}structs or special atoms. - âĻ Just functions - No complex custom pipe operators or DSL
- ðŠķ Zero dependencies - Lightweight and fast
- ðĶ List operations - Combine results with
all, extract withvalues, split withpartition - ⥠Lazy evaluation - Avoid computation with
_lazyvariants - ð Transformations - Replace, flatten, and transform results
Installation
Add err to your list of dependencies in mix.exs:
def deps do
[
{:err, "~> 0.2"}
]
endUsage
Wrap values
iex> Err.ok(42)
{:ok, 42}
iex> Err.error(:timeout)
{:error, :timeout}Unwrap with defaults
iex> Err.unwrap_or({:ok, "config.json"}, "default.json")
"config.json"
iex> Err.unwrap_or({:error, :not_found}, "default.json")
"default.json"Lazy unwrapping (function only called when needed)
iex> Err.unwrap_or_lazy({:error, :enoent}, fn reason -> "Error: #{reason}" end)
"Error: enoent"Transform success values
iex> Err.map({:ok, 5}, fn num -> num * 2 end)
{:ok, 10}Transform error values
iex> Err.map_err({:error, :timeout}, fn reason -> "#{reason}_error" end)
{:error, "timeout_error"}Chain operations
iex> Err.and_then({:ok, 5}, fn num -> {:ok, num * 2} end)
{:ok, 10}Flatten nested results
iex> Err.flatten({:ok, {:ok, 1}})
{:ok, 1}Eager fallback
iex> Err.or_else({:error, :cache_miss}, {:ok, "disk.db"})
{:ok, "disk.db"}Lazy fallback
Err.or_else_lazy({:error, :cache_miss}, fn _reason ->
{:ok, load_from_disk()}
end)Combine results (fail fast)
iex> Err.all([{:ok, 1}, {:ok, 2}, {:ok, 3}])
{:ok, [1, 2, 3]}
iex> Err.all([{:ok, 1}, {:error, :timeout}])
{:error, :timeout}Extract ok values
iex> Err.values([{:ok, 1}, {:error, :x}, {:ok, 2}])
[1, 2]Split into ok and error lists
iex> Err.partition([{:ok, 1}, {:error, "a"}, {:ok, 2}])
{[1, 2], ["a"]}Check if result is ok
def process(result) when Err.is_ok(result) do
# handle ok
endCheck if result is error
def process(result) when Err.is_err(result) do
# handle error
endReal-World Example
def fetch_user_profile(user_id) do
user_id
|> fetch_user()
|> Err.and_then(&load_profile/1)
|> Err.and_then(&enrich_with_stats/1)
|> Err.or_else_lazy(fn _error ->
{:ok, %{name: "Guest", stats: %{}}}
end)
end
Summary
Types
An option type representing either some value or none.
A result type representing either success or failure.
Functions
Combines a list of values into a single result.
Chains the result by calling fun when the value is present.
Wraps value in an {:error, value} tuple.
Returns the wrapped value from an {:ok, value} tuple or raises the provided exception.
Returns the wrapped error from an {:error, reason} tuple or raises the provided exception.
Flattens a nested result into a single layer.
Checks if a value is an {:error, ...} result tuple.
Checks if a value is an {:ok, ...} result tuple.
Checks if a value is "some" (not nil).
Transforms the success value inside an {:ok, value} tuple or some value by applying a function to it.
Transforms the error inside an {:error, reason} tuple by applying a function to it.
Wraps value in an {:ok, value} tuple.
Returns the first value if it is present/successful, otherwise returns the second value.
Returns the first value if it is present/successful, otherwise calls the function to compute an alternative value.
Splits a list of results into ok values and error values.
Replaces the value inside an {:ok, value} tuple with a new value.
Replaces the error inside an {:error, reason} tuple with a new value.
Replaces the error inside an {:error, reason} tuple by calling a function.
Replaces the value inside an {:ok, value} tuple by calling a function.
Returns the wrapped value or default when the result is error or value is empty.
Returns the wrapped value or computes it from default_fun when the result is an error or value
is empty.
Extracts all success values from a list of results.
Types
@type option() :: any() | nil
An option type representing either some value or none.
Can be:
value- Some value is presentnil- No value (none)
@type result() :: tuple()
A result type representing either success or failure.
Can be:
{:ok, value}- A successful result with a value{:error, error}- A failed result with an error- Any tuple starting with
:okor:error(supports multiple elements)
Functions
Combines a list of values into a single result.
- If all values are
{:ok, value}, returns{:ok, list_of_values}. - If any value is an error, returns the first error encountered (fail fast).
- If any value is
nil, returnsnil
Examples
iex> Err.all([{:ok, 1}, {:ok, 2}, {:ok, 3}])
{:ok, [1, 2, 3]}
iex> Err.all([{:ok, 1}, {:error, :timeout}, {:ok, 3}])
{:error, :timeout}
iex> Err.all([{:ok, 1}, nil, {:ok, 3}])
nil
iex> Err.all([])
{:ok, []}
iex> Err.all([{:ok, "a"}, {:ok, "b"}])
{:ok, ["a", "b"]}
Chains the result by calling fun when the value is present.
For {:ok, value} the extracted value (or list of values) is passed to fun.
Error tuples and nil are returned unchanged, allowing the pipeline to short-circuit.
Examples
iex> Err.and_then({:ok, 5}, fn num -> num * 2 end)
10
iex> Err.and_then(5, fn num -> num * 2 end)
10
iex> Err.and_then({:ok, :admin, %{id: 1}}, fn [role, user] -> {:ok, %{role: role, user_id: user.id}} end)
{:ok, %{role: :admin, user_id: 1}}
iex> Err.and_then({:error, :timeout}, fn num -> {:ok, num * 2} end)
{:error, :timeout}
iex> Err.and_then(nil, fn value -> {:ok, value} end)
nil
Wraps value in an {:error, value} tuple.
Examples
iex> Err.error(:timeout)
{:error, :timeout}
iex> Err.error({:validation_failed, :email})
{:error, {:validation_failed, :email}}
@spec expect!(value(), Exception.t()) :: any()
Returns the wrapped value from an {:ok, value} tuple or raises the provided exception.
For two-element result tuples ({:ok, value}) it returns value. When the tuple contains
additional metadata, it returns the remaining elements as a list.
If the value is {:error, _}, nil, or any other value, raises the provided exception.
Examples
iex> Err.expect!({:ok, "config.json"}, RuntimeError.exception("config not found"))
"config.json"
iex> Err.expect!({:ok, :user, %{role: :admin}}, RuntimeError.exception("user not found"))
[:user, %{role: :admin}]
@spec expect_err!(value(), Exception.t()) :: any()
Returns the wrapped error from an {:error, reason} tuple or raises the provided exception.
For two-element error tuples ({:error, reason}) it returns reason. When the tuple contains
additional metadata, it returns the remaining elements as a list.
If the value is {:ok, _}, nil, or any other value, raises the provided exception.
Examples
iex> Err.expect_err!({:error, :timeout}, RuntimeError.exception("expected an error"))
:timeout
iex> Err.expect_err!({:error, 404, "Not Found"}, RuntimeError.exception("expected an error"))
[404, "Not Found"]
Flattens a nested result into a single layer.
If the outer result is {:ok, inner} and inner is also a result tuple,
returns the inner result. Otherwise returns the value unchanged.
Examples
iex> Err.flatten({:ok, {:ok, 1}})
{:ok, 1}
iex> Err.flatten({:ok, {:ok, 1, :meta}})
{:ok, 1, :meta}
iex> Err.flatten({:ok, {:error, :timeout}})
{:error, :timeout}
iex> Err.flatten({:error, :failed})
{:error, :failed}
iex> Err.flatten({:ok, "value"})
{:ok, "value"}
Checks if a value is an {:error, ...} result tuple.
Returns true for any tuple starting with :error, false otherwise.
Allowed in guard tests.
Examples
iex> Err.is_err({:error, :timeout})
true
iex> Err.is_err({:error, 404, "Not Found"})
true
iex> Err.is_err({:ok, 1})
false
iex> Err.is_err(nil)
false
iex> Err.is_err("error")
false
def my_function(result) when is_err(result)
Checks if a value is an {:ok, ...} result tuple.
Returns true for any tuple starting with :ok, false otherwise.
Allowed in guard tests.
Examples
iex> Err.is_ok({:ok, 1})
true
iex> Err.is_ok({:ok, 1, 2})
true
iex> Err.is_ok({:error, :timeout})
false
iex> Err.is_ok(nil)
false
iex> Err.is_ok("value")
false
def my_function(result) when is_ok(result)
Checks if a value is "some" (not nil).
Returns true for any value except nil.
Allowed in guard tests.
Examples
iex> Err.is_some(1)
true
iex> Err.is_some("hello")
true
iex> Err.is_some({:ok, 1})
true
iex> Err.is_some(false)
true
iex> Err.is_some(nil)
false
def my_function(value) when is_some(value)
Transforms the success value inside an {:ok, value} tuple or some value by applying a function to it.
For Result types ({:ok, value} or {:error, reason}), applies the function to the value
if it's {:ok, _}, otherwise returns the error unchanged.
For Option types (nil or any value), applies the function to the value if it's not nil,
otherwise returns nil.
Examples
iex> Err.map({:ok, 5}, fn num -> num * 2 end)
{:ok, 10}
iex> Err.map({:ok, "hello"}, &String.upcase/1)
{:ok, "HELLO"}
iex> Err.map({:error, :timeout}, fn num -> num * 2 end)
{:error, :timeout}
iex> Err.map(nil, fn num -> num * 2 end)
nil
iex> Err.map("hello", &String.upcase/1)
"HELLO"
Transforms the error inside an {:error, reason} tuple by applying a function to it.
For Result types ({:ok, value} or {:error, reason}), applies the function to the error
if it's {:error, _}, otherwise returns the ok tuple unchanged.
Ignores nil and non-Result values, returning them unchanged.
Examples
iex> Err.map_err({:error, 404}, fn code -> "HTTP #{code}" end)
{:error, "HTTP 404"}
iex> Err.map_err({:ok, "success"}, fn reason -> "#{reason}_error" end)
{:ok, "success"}
iex> Err.map_err(nil, fn reason -> "#{reason}_error" end)
nil
iex> Err.map_err(404, fn reason -> "#{reason}_error" end)
404
Wraps value in an {:ok, value} tuple.
Examples
iex> Err.ok(%{id: 1, email: "john@example.com"})
{:ok, %{email: "john@example.com", id: 1}}
iex> Err.ok({:ok, 100})
{:ok, {:ok, 100}}
Returns the first value if it is present/successful, otherwise returns the second value.
For Result types ({:ok, value} or {:error, reason}), returns the first value if it's {:ok, _},
otherwise returns the second value.
For Option types (nil or any value), returns the first value if it's not nil,
otherwise returns the second value.
Examples
iex> Err.or_else({:ok, "cache.db"}, {:ok, "disk.db"})
{:ok, "cache.db"}
iex> Err.or_else({:ok, "cache.db"}, {:error, :unavailable})
{:ok, "cache.db"}
iex> Err.or_else({:error, :cache_miss}, {:ok, "disk.db"})
{:ok, "disk.db"}
iex> Err.or_else({:error, :cache_miss}, {:error, :disk_full})
{:error, :disk_full}
iex> Err.or_else("primary", "backup")
"primary"
iex> Err.or_else(nil, "backup")
"backup"
Returns the first value if it is present/successful, otherwise calls the function to compute an alternative value.
For Result types ({:ok, value} or {:error, reason}), returns the first value if it's {:ok, _},
otherwise calls the function with the error reason.
For Option types (nil or any value), returns the first value if it's not nil,
otherwise calls the function.
This is the lazy version of or_else/2 - the function is only called when needed.
Examples
iex> Err.or_else_lazy({:ok, "cache.db"}, fn _ -> {:ok, "disk.db"} end)
{:ok, "cache.db"}
iex> Err.or_else_lazy({:error, :cache_miss}, fn _reason -> {:ok, "disk.db"} end)
{:ok, "disk.db"}
iex> Err.or_else_lazy({:error, :timeout}, fn reason -> {:error, "Fallback failed: #{reason}"} end)
{:error, "Fallback failed: timeout"}
iex> Err.or_else_lazy("primary", fn _ -> "backup" end)
"primary"
iex> Err.or_else_lazy(nil, fn _ -> "backup" end)
"backup"
Splits a list of results into ok values and error values.
Returns a tuple {ok_values, error_values} where:
ok_valuescontains all values from{:ok, value}tupleserror_valuescontains all values from{:error, reason}tuples
Any other value is ignored.
Examples
iex> Err.partition([{:ok, 1}, {:error, "a"}, {:ok, 2}])
{[1, 2], ["a"]}
iex> Err.partition([1, nil])
{[], []}
iex> Err.partition([{:ok, "x"}, {:ok, "y"}])
{["x", "y"], []}
iex> Err.partition([{:error, :timeout}, {:error, :crash}])
{[], [:timeout, :crash]}
iex> Err.partition([])
{[], []}
Replaces the value inside an {:ok, value} tuple with a new value.
If the result is {:ok, _}, returns {:ok, new_value}.
Otherwise returns the original value unchanged.
Examples
iex> Err.replace({:ok, "old"}, "new")
{:ok, "new"}
iex> Err.replace({:error, :timeout}, 999)
{:error, :timeout}
iex> Err.replace(nil, 999)
nil
iex> Err.replace(100, 999)
100
Replaces the error inside an {:error, reason} tuple with a new value.
If the result is {:error, _}, returns {:error, new_error}.
Otherwise returns the original value unchanged.
Examples
iex> Err.replace_err({:error, :timeout}, :network_error)
{:error, :network_error}
iex> Err.replace_err({:error, 404}, :not_found)
{:error, :not_found}
iex> Err.replace_err({:ok, 1}, :error)
{:ok, 1}
iex> Err.replace_err(nil, :error)
nil
Replaces the error inside an {:error, reason} tuple by calling a function.
If the result is {:error, _}, calls the function and returns {:error, result}.
Otherwise returns the original value unchanged without calling the function.
This is the lazy version of replace_err/2 - the function is only called when needed.
Examples
iex> Err.replace_err_lazy({:error, 404}, fn value -> "Status: #{value}" end)
{:error, "Status: 404"}
iex> Err.replace_err_lazy({:ok, 1}, fn _ -> :error end)
{:ok, 1}
iex> Err.replace_err_lazy(nil, fn _ -> :error end)
nil
Replaces the value inside an {:ok, value} tuple by calling a function.
If the result is {:ok, _}, calls the function and returns {:ok, result}.
Otherwise returns the original value unchanged without calling the function.
This is the lazy version of replace/2 - the function is only called when needed.
Examples
iex> Err.replace_lazy({:ok, 1}, fn value -> value + 1 end)
{:ok, 2}
iex> Err.replace_lazy({:error, :timeout}, fn value -> value + 1 end)
{:error, :timeout}
iex> Err.replace_lazy(nil, fn value -> value + 1 end)
nil
Returns the wrapped value or default when the result is error or value is empty.
For two-element result tuples ({:ok, value}) it returns value. When the tuple
contains additional metadata, it returns the remaining elements as a list.
Accepts nil, any {:ok, value} or {:error, reason} tuple (with or without extra metadata),
and other terms.
Examples
iex> Err.unwrap_or({:ok, "config.json"}, "default.json")
"config.json"
iex> Err.unwrap_or({:ok, :user, %{role: :admin}}, [])
[:user, %{role: :admin}]
iex> Err.unwrap_or({:error, :not_found}, "default.json")
"default.json"
iex> Err.unwrap_or(nil, "default.json")
"default.json"
Returns the wrapped value or computes it from default_fun when the result is an error or value
is empty.
For successful tuples ({:ok, value}) the unwrapped value is returned. When the tuple contains
extra data, the remaining elements are returned as a list. For error tuples the extracted value(s)
are passed to default_fun.
The function receives the extracted value(s): a single value for two-element tuples or a list for larger tuples.
This is the lazy version of unwrap_or/2 - the function is only called when needed.
Examples
iex> Err.unwrap_or_lazy({:ok, "config.json"}, fn _ -> "default.json" end)
"config.json"
iex> Err.unwrap_or_lazy({:ok, :admin, %{perms: [:read]}}, fn _ -> [] end)
[:admin, %{perms: [:read]}]
iex> Err.unwrap_or_lazy({:error, :enoent}, fn reason -> "Error: #{reason}" end)
"Error: enoent"
iex> Err.unwrap_or_lazy(nil, fn _ -> %{role: :guest} end)
%{role: :guest}
Extracts all success values from a list of results.
Returns a list containing all values, except {:error, _} tuples or nil.
Examples
iex> Err.values([{:ok, 1}, {:error, :timeout}, {:ok, 2}])
[1, 2]
iex> Err.values([{:ok, 1}, nil, 2])
[1, 2]
iex> Err.values([{:ok, "a"}, {:ok, "b"}])
["a", "b"]
iex> Err.values([{:error, :x}, {:error, :y}])
[]
iex> Err.values([1])
[1]
iex> Err.values([])
[]