defmodule Monad.Result do @moduledoc """ A monad that represents success and failure conditions. In a series of bind operations, if any function returns an error monad, the following binds are skipped. This allows for easy flow control for both success and error cases. """ @typedoc """ The possible types of results that can occur (i.e. success and failure). """ @type result_type :: :ok | :error @typedoc """ The standard tuple format for representing success and error states. These tuples are easily converted to a `Monad.Result`. """ @type result_tuple :: {result_type, term} use Monad.Behaviour @opaque t :: %__MODULE__{type: result_type, value: term, error: term} @doc false defstruct type: :ok, value: nil, error: nil @doc """ Wraps a value in a success monad. iex> s = success 42 iex> s.value 42 iex> s.error nil """ @spec success(term) :: t def success(value), do: %Monad.Result{type: :ok, value: value} @doc """ Wraps a value in an error monad. iex> e = error "Failed" iex> e.value nil iex> e.error "Failed" """ @spec error(term) :: t def error(error), do: %Monad.Result{type: :error, error: error} @doc """ Returns true if the given monad contains a success state. iex> s = success 42 iex> success? s true """ @spec success?(t) :: boolean def success?(result), do: result.type == :ok @doc """ Returns true if the given monad contains an error state. iex> e = error "Failed" iex> error? e true """ @spec error?(t) :: boolean def error?(result), do: result.type == :error @doc """ Converts a standard success/failure tuple to a `Monad.Result`. iex> s = from_tuple {:ok, 42} iex> s.value 42 iex> s.error nil iex> e = from_tuple {:error, "Failed"} iex> e.value nil iex> e.error "Failed" """ @spec from_tuple(result_tuple) :: t def from_tuple({:ok, value}), do: success(value) def from_tuple({:error, reason}), do: error(reason) @doc """ Converts the `Monad.Result` to a tagged tuple. iex> s = success 42 iex> to_tuple s {:ok, 42} iex> e = error :badarg iex> to_tuple e {:error, :badarg} """ @spec to_tuple(t) :: result_tuple def to_tuple(%__MODULE__{type: :ok, value: value}), do: {:ok, value} def to_tuple(%__MODULE__{type: :error, error: error}), do: {:error, error} @doc """ Unwraps the value from a success monad. Does not work with error monads. iex> s = success 42 iex> unwrap! s 42 """ @spec unwrap!(t) :: term def unwrap!(%Monad.Result{type: :ok, value: value}), do: value @doc """ Callback implementation of `Monad.Behaviour.return/1`. Wraps a value in a success monad. iex> s = return 42 iex> s.value 42 iex> s.error nil """ @spec return(term) :: t def return(value), do: success(value) @doc """ Callback implementation of `Monad.Behaviour.bind/2`. If the monad contains a success state, then the value is unwrapped and applied to `fun`. For monads containing an error state, the error is returned as is. iex> s = success 42 iex> r = bind s, (& success &1 * 2) iex> r.value 84 iex> r.error nil iex> s = success 42 iex> r = bind s, fn _ -> error "Failed" end iex> r.value nil iex> r.error "Failed" """ @spec bind(t, (term -> t)) :: t def bind(result = %Monad.Result{type: :error}, _fun), do: result def bind(result = %Monad.Result{type: :ok}, fun) when is_function(fun, 1) do result |> unwrap! |> fun.() end end