defmodule Algae.State do @moduledoc ~S""" `Algae.State` describes a wrapped function that can be used to pass around some "hidden" pure state. This has numerous applications, but the primary advantage is purity. The state gets passed around with the value, and the monadic DSL helps it feel more natural than passing everything around by hand. In many ways, `Algae.State` is a generalization of `Algae.Reader` and `Algae.Writer`. See [Thee Useful Monads](http://adit.io/posts/2013-06-10-three-useful-monads.html#the-state-monad) a nice, illustrated guide to how these work and relate. ## Anatomy # To pass in concrete values ↓ %Algae.State{runner: fn access -> {value, state} end} ↑ ↑ # "explicit" value position "hidden" state position ## Examples iex> use Witchcraft ...> ...> %Algae.State{} ...> |> monad do ...> name <- get() ...> let result = "Hello, #{name}!" ...> ...> put result ...> modify &String.upcase/1 ...> ...> return result ...> end ...> |> run("world") { "Hello, world!", "HELLO, WORLD!" } iex> use Witchcraft ...> ...> pop = fn -> state(fn([x | xs]) -> {x, xs} end) end ...> pull = fn -> state(fn(list = [x | _]) -> {x, list} end) end ...> push = &state(fn(xs) -> {%Witchcraft.Unit{}, [&1 | xs]} end) ...> ...> %Algae.State{} ...> |> monad do ...> push.(["a"]) ...> push.(["b"]) ...> push.(["c"]) ...> push.(["d"]) ...> push.(["e"]) ...> ...> z <- pop.() ...> y <- pop.() ...> x <- pop.() ...> ...> push.(x <> y <> z) ...> pull.() ...> end ...> |> evaluate([]) ["c", "d", "e"] """ alias __MODULE__ alias Witchcraft.Unit use Witchcraft @type runner :: (any() -> {any(), any()}) @type t :: %State{runner: runner()} defstruct [runner: &State.default/1] @spec default(any()) :: {integer(), any()} defp default(s), do: {s, s} @doc """ Construct a new `Algae.State` struct from a state runner in the form `fn x -> {y, z} end` ## Examples iex> new(fn x -> {x + 1, x} end).runner.(42) {43, 42} """ @spec new(State.runner()) :: State.t() def new(runner), do: %State{runner: runner} @doc """ Alias for `new/1` that reads better when importing the module. ## Examples iex> state(fn x -> {x + 1, x} end).runner.(42) {43, 42} """ @spec state(State.runner()) :: State.t() def state(runner), do: new(runner) @doc """ Extract the runner from an `Algae.State`. Can be used as a curried version of `run/2`. ## Examples iex> inner = fn x -> {0, x} end ...> ...> run(%Algae.State{runner: inner}).(42) == inner.(42) true """ @spec run(State.t()) :: State.runner() def run(%State{runner: fun}), do: fun @doc """ Run an `Algae.State` by passing in some initial state to actualy run the enclosed state runner. ## Examples iex> use Witchcraft ...> ...> %Algae.State{} ...> |> of(2) ...> |> run(0) {2, 0} """ @spec run(State.t(), any()) :: any() def run(%State{runner: fun}, arg), do: fun.(arg) @doc """ Set the stateful position of an `Algae.Struct`. Not unlike `Algae.Writer.tell/1`. ## Examples iex> 1 ...> |> put() ...> |> run(0) {%Witchcraft.Unit{}, 1} """ @spec put(any()) :: State.t() def put(s), do: State.new(fn _ -> {%Unit{}, s} end) @doc ~S""" Run a function over the "state" portion of the runner. ## Examples iex> fn x -> x + 1 end ...> |> modify() ...> |> run(42) {%Witchcraft.Unit{}, 43} iex> use Witchcraft ...> ...> %Algae.State{} ...> |> monad do ...> name <- get() ...> ...> put "State" ...> modify &String.upcase/1 ...> ...> return "Hello, #{name}!" ...> end ...> |> run("world") {"Hello, world!", "STATE"} """ @spec modify((any() -> any())) :: State.t() def modify(fun), do: State.new(fn s -> {%Unit{}, fun.(s)} end) @doc """ Set both sides of an `Algae.State` struct. ## Examples iex> run(get(), 1) {1, 1} """ @spec get() :: State.t() def get, do: State.new(fn a -> {a, a} end) @doc """ Set both sides of an `Algae.State` struct, plus running a function over the value portion of the inner state. ## Examples iex> fn x -> x * 10 end ...> |> get() ...> |> run(4) {40, 4} """ @spec get((any() -> any())) :: State.t() def get(fun) do monad %Algae.State{} do s <- get() return fun.(s) end end @doc ~S""" Run the enclosed `Algae.State` runner, and return the value (no state). ## Examples iex> use Witchcraft ...> ...> %Algae.State{} ...> |> monad do ...> name <- get() ...> put "Ignored" ...> return "Hello, #{name}!" ...> end ...> |> evaluate("world") "Hello, world!" """ @spec evaluate(State.t(), any()) :: any() def evaluate(state, value) do state |> run(value) |> elem(0) end @doc ~S""" Run the enclosed `Algae.State` runner, and return the state (no value). ## Examples iex> fn x -> x + 1 end ...> |> get() ...> |> execute(1) 1 iex> use Witchcraft ...> ...> %Algae.State{} ...> |> monad do ...> whatevs <- get() ...> put "State" ...> return "Hello, #{whatevs}!" ...> end ...> |> execute("world") "State" """ @spec execute(State.t(), any()) :: any() def execute(state, value) do state |> run(value) |> elem(1) end end