Current section
Files
Jump to
Current section
Files
lib/reather.ex
defmodule Reather do
defstruct [:reather]
require Reather.Macros
import Reather.Macros
alias Reather.Either
@moduledoc """
Reather is the combined form of Reader and Either monad.
A `Reather` wrapps an environment and the child functions can
use the environment to access the values.
The evaluation of `Reather` is lazy, so it's never computed until
explicitly call `Reather.run/2`.
"""
defmacro __using__([]) do
quote do
import Reather.Macros, only: [reather: 1, reather: 2, reatherp: 2]
require Reather.Macros
alias Reather.Either
end
end
@doc """
Get the current environment.
"""
def ask(), do: Reather.new(fn env -> {:ok, env} end)
@doc """
Run the reather.
"""
def run(%Reather{reather: fun}, arg \\ %{}) do
fun.(arg)
end
@doc """
Map a function to the reather.
`map` is lazy, so it's never computed until explicitly call
`Reather.run/2`.
## Examples
iex> r = reather do
...> x <- {:ok, 1}
...> x
...> end
iex> r
...> |> Reather.map(fn x -> x + 1 end)
...> |> Reather.run()
{:ok, 2}
"""
def map(r, fun) do
reather do
x <- r
fun.(x)
end
end
@doc """
Transform a list of reathers to an reather of a list.
This operation is lazy, so it's never computed until
explicitly call `Reather.run/2`.
## Examples
iex> r = [{:ok, 1}, {:ok, 2}, {:ok, 3}]
...> |> Enum.map(&Reather.of/1)
...> |> Reather.traverse()
iex> Reather.run(r)
{:ok, [1, 2, 3]}
iex> r = [{:ok, 1}, {:error, "error"}, {:ok, 3}]
...> |> Enum.map(&Reather.of/1)
...> |> Reather.traverse()
iex> Reather.run(r)
{:error, "error"}
"""
def traverse(traversable) when is_list(traversable) do
Reather.new(fn env ->
traversable
|> Enum.map(fn %Reather{} = r ->
Reather.run(r, env)
end)
|> Either.traverse()
end)
end
@doc """
Inspect the reather.
**Warning**: It _runs_ the reather. So, the provided
reather should not have side effects.
"""
def inspect(%Reather{} = r, opts \\ []) do
Reather.new(fn env ->
r |> Reather.run(env) |> IO.inspect(opts)
end)
end
@doc """
Create a new `Reather` from the function.
"""
def new(fun), do: %Reather{reather: fun}
@doc """
Create a `Reather` from the value.
"""
def of(v), do: Reather.new(fn _ -> Either.new(v) end)
@doc """
Create a `Reather` from the value.
If the value is `Reather`, it will be returned as is.
## Examples
iex> %Reather{} = Reather.wrap(:ok)
iex> r = %Reather{}
iex> ^r = Reather.wrap(r)
"""
def wrap(%Reather{} = r), do: r
def wrap(v), do: of(v)
end