Monaditto

Elixir CIHex.pm VersionHex.pm DownloadsDocumentation

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

Why though?

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!

The Better Way™

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.

Installation

Add monaditto to your list of dependencies in mix.exs:

def deps do
  [
    {:monaditto, "~> 0.4.0"}
  ]
end

Quick Start

# 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]}

Available Functions

Philosophy

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.

Documentation

Full documentation is available at HexDocs (when we publish it).

Contributing

Found a bug? Have an idea? PRs welcome! Just remember: keep it simple, keep it pragmatic, keep it Elixir-y.

License

MIT License. Because sharing is caring, and lawyers are expensive.


Made with ❤️ and a healthy dose of functional programming enthusiasm