defmodule Brex.Result.Base do @moduledoc """ Tools for doing basic result tuple manipulations. """ @type s(x) :: {:ok, x} | {:error, any} @type t(x) :: :ok | s(x) @type p() :: :ok | {:error, any} @type s() :: s(any) @type t() :: t(any) @doc """ Wraps value in an `ok` tuple. Will be inlined at compile time. ## Typespec: ok(a) :: s(a) when a: var """ @doc since: "0.1.0" defmacro ok(val), do: {:ok, val} @doc """ Wraps value in an `error` tuple Will be inlined at compile time. ## Typespec: error(any) :: t() """ @doc since: "0.1.0" defmacro error(r), do: {:error, r} @doc """ Takes in a tuple and function from plain value to `{:ok, any} | {:error, any}`. Applies the function to the value within the `ok` tuple or propogates the `error`. ## Examples: iex> bind({:ok, 1}, fn x -> if x == 1, do: {:ok, 2}, else: {:error, "not_one"} end) {:ok, 2} iex> bind({:ok, 4}, fn x -> if x == 1, do: {:ok, 2}, else: {:error, "not_one"} end) {:error, "not_one"} iex> bind({:error, 4}, fn x -> if x == 1, do: {:ok, 2}, else: {:error, "not_one"} end) {:error, 4} """ @doc updated: "0.3.0" @doc since: "0.1.0" @spec bind(s(a), (a -> s(b))) :: s(b) when a: var, b: var def bind({:error, r}, _), do: {:error, r} def bind({:ok, v}, f) do case f.(v) do {:error, r} -> {:error, r} {:ok, val} -> {:ok, val} end end @doc """ This is infix `bind/2` Has same syntax restrictions as pipe. Defined as a macro for syntatic purposes. ## Examples: def sgn(x) do if x > 0 do {:ok, "pos"} else {:error, "neg"} end end def two_args(x, y), do: {:ok, x - y} {:ok, 1} ~> sgn = {:ok, "pos"} {:ok, -3} ~> sgn = {:error, "neg"} {:error, 2} ~> sgn = {:error, 2} {:ok, 3} ~> two_args(2) = {:ok, 1} ## Typespec: t(a) ~> (a -> t(b)) :: t(b) when a: var, b: var """ @doc updated: "0.4.0" @doc since: "0.1.0" defmacro arg ~> fun do quote do unquote(__MODULE__).bind(unquote(arg), fn x -> x |> unquote(fun) end) end end @doc """ Takes in a tuple and a function from plain value to plain value. Applies the function to the value within the `ok` tuple or propogates `error`. ## Examples: iex> {:ok, 6} ...> |> fmap(fn x -> x+2 end) {:ok, 8} iex> {:error, 6} ...> |> fmap(fn x -> x+2 end) {:error, 6} """ @doc since: "0.1.0" @spec fmap(s(a), (a -> b)) :: s(b) when a: var, b: var def fmap(m, f), do: bind(m, &{:ok, f.(&1)}) @doc """ Ignores the value in an `ok` tuple and just returns `:ok`. Still shortcircuits on `error`. ## Examples: iex> {:ok, 2} ...> |> ignore :ok iex> :ok ...> |> ignore :ok iex> {:error, :not_found} ...> |> ignore {:error, :not_found} """ @doc since: "0.2.0" @spec ignore(t()) :: p() def ignore({:error, r}), do: {:error, r} def ignore({:ok, _val}), do: :ok def ignore(:ok), do: :ok @doc """ Extracts the value or reason from the tuple. Caution: If given an `error` tuple it raise an exception! """ @doc updated: "0.3.0" @doc since: "0.1.0" @spec extract!(s(a)) :: a when a: var def extract!({:error, _} = ma) do raise ArgumentError, "`extract` expects an ok tuple, \"#{inspect(ma)}\" given." end def extract!({:ok, value}), do: value end