"Behold, traveler, and let thine eyes witness monads for Elixir, lest they transform thee into a basement-dwelling dev and hikki of code"
A pragmatic monad library for Elixir that works with your existing {:ok, value} | {:error, reason}
code. No fancy wrapper types, no category theory PhD required, just good old Elixir tuples doing monad things.
You're already using monads every day in Elixir:
jose = %{first_name: "Jose", last_name: "Valim"}
{:ok, "Jose"} = Map.fetch(jose, :first_name) # Maybe monad says hello
:error = Map.fetch(jose, :age) # Maybe monad says goodbye
But then you end up with code like this:
case fetch_user(id) do
{:ok, user} ->
case get_email(user) do
{:ok, email} ->
case send_notification(email) do
{:ok, result} -> {:ok, result}
{:error, reason} -> {:error, reason}
end
{:error, reason} -> {:error, reason}
end
{:error, reason} -> {:error, reason}
end
Of course, you can make some improvements:
with {:ok, user} <- fetch_user(id),
{:ok, email} <- get_email(user) do
send_notification(email)
end
But with Monaditto
you can rock it even better!
user_id
|> fetch_user() # {:ok, %User{}} | {:error, :not_found}
|> Monad.map(&get_email/1) # {:ok, "user@example.com"} | {:error, :not_found}
|> Monad.map(&send_notification/1) # {:ok, :sent} | {:error, :not_found}
Clean, readable, and your error handling is automagically short-circuited. Like with
statements, but with more style points.
Add monaditto
to your list of dependencies in mix.exs
:
def deps do
[
{:monaditto, "~> 0.3.0"}
]
end
# Basic mapping
{:ok, "hello"}
|> Monad.map(&String.upcase/1)
|> Monad.map(&String.reverse/1)
# => {:ok, "OLLEH"}
# Error short-circuiting
{:error, :oops}
|> Monad.map(&String.upcase/1) # Nope, not happening
|> Monad.map(&String.reverse/1) # Still nope
# => {:error, :oops}
# Safe operations
Monad.safe(fn -> 1 / 0 end)
# => {:error, %ArithmeticError{...}}
# Processing lists
[{:ok, 1}, {:ok, 2}, {:ok, 3}]
|> Monad.traverse(&({:ok, &1 * 2}))
# => {:ok, [2, 4, 6]}
map/2
- Transform success valuesmap_error/2
- Transform error valuesflat_map/2
- Chain operations that return resultsbimap/3
- Transform both success and error casespeek/2
- Side effects without changing the valuetraverse/2
- Map and sequence combinedsequence/2
- Unwrap lists of resultssafe/2
- Wrap dangerous operationsunwrap/2
&unwrap!/1
- Extract values (carefully)any_error?/1
&all_ok?/1
- Validation helpers
This library embraces Elixir's "let it crash" mentality while giving you tools to handle errors gracefully when you need to. We're not trying to turn Elixir into Haskell (though Haskell is lovely). We're just making your existing error-handling patterns more composable and less nested.
No magic, no surprises, just functions that do what they say on the tin.
Full documentation is available at HexDocs (when we publish it).
Found a bug? Have an idea? PRs welcome! Just remember: keep it simple, keep it pragmatic, keep it Elixir-y.
MIT License. Because sharing is caring, and lawyers are expensive.
Made with ❤️ and a healthy dose of functional programming enthusiasm