Current section
Files
Jump to
Current section
Files
lib/monad/reader.ex
defmodule Monad.Reader do
@moduledoc """
The reader monad is great for situations when you need to share some
environment or state between several operations.
Reader monads are great for dependency injection. The environment is specified
separately from the operations.
## Example
iex> import Curry
iex> use Monad.Operators
iex> env = %{dev: %{url: "http://www.example.com/dev"}, prod: %{url: "https://www.example.com/prod"}}
iex> r = reader(& env[&1])
...> ~>> (& return(&1[:url]))
...> ~>> (& return(&1 <> "/index.html"))
iex> fun = runReader r
iex> fun.(:dev)
"http://www.example.com/dev/index.html"
iex> fun.(:prod)
"https://www.example.com/prod/index.html"
iex> reader = curry(& &1 + &2) <|> reader(& &1 * 2) <~> reader(& &1 * &1)
iex> fun = runReader reader
iex> fun.(2)
8
iex> fun.(-12)
120
"""
use Monad.Behaviour
@opaque t :: %__MODULE__{fun: (term -> term)}
@doc false
defstruct fun: nil
@doc """
Wraps `fun` in a reader monad.
iex> r = reader &(&1)
iex> r.fun.(4)
4
"""
@spec reader((term -> term)) :: t
def reader(fun), do: %Monad.Reader{fun: fun}
@doc """
Unwraps the function from the reader.
iex> r = reader &(&1 * 2)
iex> f = runReader r
iex> f.(3)
6
"""
@spec runReader(t) :: (term -> term)
def runReader(reader), do: reader.fun
@doc """
Callback implementation of `Monad.Behaviour.return/1`.
Converts `value` into a function that always returns `value`. Then wraps that
function into a reader monad.
iex> r = return 42
iex> r.fun.(:unused)
42
"""
@spec return(term) :: t
def return(value), do: %Monad.Reader{fun: fn _ -> value end}
@doc """
Callback implementation of `Monad.Behaviour.bind/2`.
iex> r = reader &(&1 * 2)
iex> bind r, &(&1 + 3)
iex> f = runReader r
iex> f.(10)
20
"""
@spec bind(t, (term -> t)) :: t
def bind(reader, fun) when is_function(fun, 1) do
reader fn x ->
fun1 = reader |> runReader
fun2 = x |> fun1.() |> fun.() |> runReader
fun2.(x)
end
end
end