Packages

(OK fork) Elegant error/exception handling in Elixir, with result monads.

Current section

Files

Jump to
res lib result for.ex
Raw

lib/result/for.ex

defmodule Result.For do
defmacro for(do: binding, after: yield_block) do
{:__block__, _env, bindings} = wrap_code_block(binding)
safe_yield_block =
quote location: :keep, generated: true do
unquote(__MODULE__).wrap(unquote(yield_block))
end
expand_bindings(bindings, safe_yield_block)
end
defmacro for(do: bind_block, after: yield_block, else: exception_clauses) do
{:__block__, _env, bindings} = wrap_code_block(bind_block)
safe_yield_block =
quote location: :keep do
unquote(__MODULE__).wrap(unquote(yield_block))
end
quote location: :keep, generated: true do
with {:error, reason} <- unquote(expand_bindings(bindings, safe_yield_block)) do
case reason do
unquote(exception_clauses)
end
end
end
end
defmacro for(_) do
description = """
Result.for/1 requires `do` and `after` clauses. e.g.
Result.for do
a <- safe_div(8, 2)
b <- safe_div(a, 2)
after
a + b
rescue
:zero_division ->
{:error, :nan}
end
"""
raise %SyntaxError{
file: __CALLER__.file,
line: __CALLER__.line,
description: description
}
end
defp wrap_code_block({:__block__, _env, _lines} = block), do: block
defp wrap_code_block({_, env, _} = expression) do
{:__block__, env, [expression]}
end
defp wrap_code_block(literal) do
{:__block__, [], [literal]}
end
defp expand_bindings([{:<-, env, [left, right]} | rest], yield_block) do
line = Keyword.get(env, :line)
normal_cases =
quote line: line, generated: true do
{:ok, unquote(left)} ->
unquote(expand_bindings(rest, yield_block))
{:error, reason} ->
{:error, reason}
end
warning_case =
quote line: line, generated: true do
return ->
raise %Result.BindError{
return: return,
lhs: unquote(Macro.to_string(left)),
rhs: unquote(Macro.to_string(right))
}
end
quote line: line do
case unquote(right) do
unquote(normal_cases ++ warning_case)
end
end
end
defp expand_bindings([normal | rest], yield_block) do
quote do
unquote(normal)
unquote(expand_bindings(rest, yield_block))
end
end
defp expand_bindings([], yield_block) do
yield_block
end
end