MonEx implements two of the most common monadic data types:
MonEx.Result— container for either a successful result or an error. Build one with the constructor macros:ok(value)for success anderror(e)for failure. Underneath it's a tuple —{:ok, value}or{:error, e}— which lines up cleanly with idiomatic Elixir return values.MonEx.Option— container for a value that may or may not be present. Usesome(value)to wrap a present value andnone()to represent its absence. Mind the parentheses — they matter when pattern matching.MonEx— utility functions (map/2,flat_map/2,foreach/2) that work uniformly on both types.
Result
Result fits naturally on top of Erlang/Elixir return conventions.
Whenever a function returns {:ok, val} or {:error, err}, MonEx
combinators apply directly. They shine in pipelines where each step
may fail. Without MonEx that typically becomes a stack of nested
case expressions:
final = case op1(x) do
{:ok, res1} ->
case op2(res1) do
{:ok, res2} -> op3(res2)
{:error, e} -> {:error, e}
end
{:error, e} -> {:error, e}
endWith flat_map/2 it collapses to:
final = op1(x) |> flat_map(&op2/1) |> flat_map(&op3/1)As soon as one step returns error(e), the rest are skipped and the
error propagates. You can then either pattern match on the outcome or
fall back to a default value or function:
case final do
ok(value) -> IO.puts(value)
error(_) -> IO.puts("Oh no, an error occurred!")
end
final |> fallback(ok("No problem, I got it"))Option
Option wraps a value: some(value) if it's there, none() if not.
The same map, flat_map, etc. work uniformly:
find_user(id)
|> map(&find_posts_by_user/1)Posts are only fetched when the user was found; otherwise none()
short-circuits through.
See MonEx.Result and MonEx.Option for the full surface.
Summary
Functions
Like map/2, but f itself returns a value of the same monadic type.
Useful for chaining operations that each can fail or return nothing —
the wrapper isn't doubled up.
Calls f on the wrapped value for its side effects, then returns the
original container unchanged. Unlike a typical foreach (which returns
Unit), this one passes the input through, so calls can be chained.
Applies f to the value inside ok/some. Returns error/none
unchanged.
Functions
Like map/2, but f itself returns a value of the same monadic type.
Useful for chaining operations that each can fail or return nothing —
the wrapper isn't doubled up.
Example
inverse = fn x ->
if x == 0, do: none(), else: some(1 / x)
end
some(5) |> flat_map(inverse) == some(0.2)
some(0) |> flat_map(inverse) == none()
Calls f on the wrapped value for its side effects, then returns the
original container unchanged. Unlike a typical foreach (which returns
Unit), this one passes the input through, so calls can be chained.
Example
some(5)
|> foreach(fn x -> IO.inspect(x) end)
|> foreach(fn x -> IO.inspect(2 * x) end)Prints 5 and then 10 on separate lines and evaluates back to some(5).
Applies f to the value inside ok/some. Returns error/none
unchanged.
Example
f = fn x -> x * 2 end
some(5) |> map(f) == some(10)
none() |> map(f) == none()
ok(5) |> map(f) == ok(10)
error(:oops) |> map(f) == error(:oops)