Skip to content

Instantly share code, notes, and snippets.

@nirev
Last active December 15, 2015 17:57
Show Gist options
  • Save nirev/b722fbbb9fdb91cd5aa8 to your computer and use it in GitHub Desktop.
Save nirev/b722fbbb9fdb91cd5aa8 to your computer and use it in GitHub Desktop.
defmodule MonadError do
defmacro __using__(_) do
quote do
import MonadError, only: [bind: 1, fail: 1, fail: 0, with: 3, either: 3, sandbox: 1]
require MonadError
end
end
defmacro sandbox(body) do
quote do
try do
unquote(Keyword.get(body, :do))
after; end
end
end
defmacro with(ctx, var, body) do
var = Macro.var(var, nil)
quote do
case unquote(ctx) do
{:cont, value} ->
unquote(var) = value
unquote(Keyword.get body, :do)
{:fail, reason} -> {:fail, reason}
_ -> {:fail, "runtime_error: {:cont, x} | {:fail, x} was expected"}
end
end
end
def from_ok2({:ok, v}), do: bind(v)
def from_ok2({:error, v}), do: fail(v)
def from_ok2(_), do: fail(:from_ok2)
def from_ok1(:ok), do: bind(nil)
def from_ok1(_), do: fail(:from_ok1)
def from_bool(true), do: bind(true)
def from_bool(_), do: fail(:from_bool)
def not_nil(nil), do: fail(:not_nil)
def not_nil(x), do: bind(x)
def fail, do: fail(nil)
def fail(x), do: {:fail, x}
def bind(x), do: {:cont, x}
def check({:cont, done}), do: {:cont, done}
def check({:fail, fail}), do: {:fail, fail}
def check(_), do: {:fail, "runtime_error: {:cont, x} | {:fail, x} was expected"}
def join(x) do
case x do
{:cont, {:cont, done}} -> {:cont, done}
{:cont, {:fail, fail}} -> {:fail, fail}
{:fail, fail} -> {:fail, fail}
_ -> {:fail, "runtime_error: {:cont, {:cont, x}} | {:cont, {:fail, x}} | {:fail, x} was expected"}
end
end
def mapM(xs, fun) do
r = Enum.reduce_while(xs, {:cont, []}, fn x, acc ->
{:cont, acc} = acc
case (fun.(x)) do
{:cont, r} -> {:cont, {:cont, [r | acc]}}
r = {:fail, _} -> {:halt, r}
end
end)
case r do
{:cont, xs} -> {:cont, Enum.reverse xs}
{:fail, msg} -> {:fail, msg}
end
end
defmacro either(value, failure, success) do
quote do
case unquote(value) do
{:cont, x} -> unquote(success).(x)
{:fail, x} -> unquote(failure).(x)
end
end
end
end
def something(params) do
repo_insert = fn x -> MonadError.from_ok2 (Repo.insert x) end
repo_update = fn x -> MonadError.from_ok2 (Repo.update x) end
repo_insert.(User.changeset(%User{}, params))
|> with(:user, do: repo_insert.(Profile.create_changeset(%Profile{}, (put_in params, ["user_id"], user.id))))
|> with(:profile, do: repo_insert.(Position.create_changeset(%Position{}, (put_in params, ["profile_id"], profile.id))))
|> with(:position, do: repo_update.(Profile.update_changeset(profile, %{position_id: position.id})))
|> with(:profile, do: bind [user: user, profile: profile, position: position])
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment