Algae.Writer (Algae v1.3.1) View Source

Algae.Writer helps capture the pattern of writing to a pure log or accumulated value, handling the bookkeeping for you.

If Algae.Reader is quasi-read-only, Algae.Writer is quasi-write-only. This is often used for loggers, but could be anything as long as the hidden value is a Witchcraft.Monoid.

There are many applications of Writers, but as an illustrative point, one could use it for logging across processes and time, since the log is carried around with the result in a pure fashion. The monadic DSL helps make using these feel more natural.

For an illustrated guide to Writers, see Thee Useful Monads.

Anatomy

  %Algae.Writer{writer: {value, log}}
                                
 # "explicit" value position    "hidden" position,
 #                               commonly used as a log

Examples

iex> use Witchcraft
...>
...> excite =
...>   fn string ->
...>     monad writer({0.0, "log"}) do
...>       tell string
...>
...>       excited <- return "#{string}!"
...>       tell " => #{excited} ... "
...>
...>       return excited
...>     end
...>   end
...>
...> {_, logs} =
...>   "Hi"
...>   |> excite.()
...>   >>> excite
...>   >>> excite
...>   |> censor(&String.trim_trailing(&1, " ... "))
...>   |> run()
...>
...> logs
"Hi => Hi! ... Hi! => Hi!! ... Hi!! => Hi!!!"

iex> use Witchcraft
...>
...> exponent =
...>   fn num ->
...>     monad writer({0, 0}) do
...>       tell 1
...>       return num * num
...>     end
...>   end
...>
...> initial = 42
...> {result, times} = run(exponent.(initial) >>> exponent >>> exponent)
...>
...> "#{initial}^#{round(:math.pow(2, times))} = #{result}"
"42^8 = 9682651996416"

Link to this section Summary

Functions

Run a writer, and run a function over the resulting log.

Copy the log into the value position. This makes it accessible in do-notation.

Similar to listen/1, but with the ability to adjust the copied log.

Construct a Algae.Writer struct from a starting value and log.

Run a function in the value portion of an Algae.Writer on the log.

Extract the enclosed value and log from an Algae.Writer.

Set the "log" portion of an Algae.Writer step

Similar to new/2, but taking a tuple rather than separate fields.

Link to this section Types

Specs

log() :: Witchcraft.Monoid.t()

Specs

t() :: %Algae.Writer{writer: writer()}

Specs

value() :: any()

Specs

writer() :: {value(), log()}

Link to this section Functions

Specs

censor(t(), (any() -> any())) :: t()

Run a writer, and run a function over the resulting log.

Examples

iex> 42
...> |> new(["hi", "THERE", "friend"])
...> |> censor(&Enum.reject(&1, fn log -> String.upcase(log) == log end))
...> |> run()
{42, ["hi", "friend"]}

iex> use Witchcraft
...>
...> 0
...> |> new(["logs"])
...> |> monad do
...>   tell ["Start"]
...>   tell ["BANG!"]
...>   tell ["shhhhhhh..."]
...>   tell ["LOUD NOISES!!!"]
...>   tell ["End"]
...>
...>   return 42
...> end
...> |> censor(&Enum.reject(&1, fn log -> String.upcase(log) == log end))
...> |> run()
{42, ["Start", "shhhhhhh...", "End"]}

Specs

listen(t()) :: t()

Copy the log into the value position. This makes it accessible in do-notation.

Examples

iex> listen(%Algae.Writer{writer: {42, "hi"}})
%Algae.Writer{writer: {{42, "hi"}, "hi"}}

iex> use Witchcraft
...>
...> monad new(1, 1) do
...>   wr <- listen tell(42)
...>   tell 43
...>   return wr
...> end
%Algae.Writer{
  writer: {{%Witchcraft.Unit{}, 42}, 85}
}

Specs

listen(t(), (log() -> log())) :: t()

Similar to listen/1, but with the ability to adjust the copied log.

Examples

iex> listen(%Algae.Writer{writer: {1, "hi"}}, &String.upcase/1)
%Algae.Writer{
  writer: {{1, "HI"}, "hi"}
}
Link to this function

new(value \\ 0, log \\ [])

View Source

Specs

new(any(), Witchcraft.Monoid.t()) :: t()

Construct a Algae.Writer struct from a starting value and log.

Examples

iex> new()
%Algae.Writer{writer: {0, []}}

iex> new("hi")
%Algae.Writer{writer: {"hi", []}}

iex> new("ohai", 42)
%Algae.Writer{writer: {"ohai", 42}}

Specs

pass(t()) :: t()

Run a function in the value portion of an Algae.Writer on the log.

Notice that the structure is similar to what somes out of listen/{1,2}

Algae.Writer{writer: {{_, function}, log}}

Examples

iex> pass(%Algae.Writer{writer: {{1, fn x -> x * 10 end}, 42}})
%Algae.Writer{writer: {1, 420}}

iex> use Witchcraft
...>
...> monad new("string", ["logs"]) do
...>   a <-  ["start"] |> tell() |> listen()
...>   tell ["middle"]
...>
...>   {value, logs} <- return a
...>   pass writer({{value, fn [log | _] -> [log | [log | logs]] end}, logs})
...>
...>   tell ["next is 42"]
...>   return 42
...> end
%Algae.Writer{
  writer: {42, ["start", "middle", "start", "start", "start", "next is 42"]}
}

Specs

run(t()) :: value()

Extract the enclosed value and log from an Algae.Writer.

Examples

iex> run(%Algae.Writer{writer: {"hi", "there"}})
{"hi", "there"}

iex> use Witchcraft
...>
...> half =
...>   fn num ->
...>     monad writer({0.0, ["log"]}) do
...>       let half = num / 2
...>       tell ["#{num} / 2 = #{half}"]
...>       return half
...>     end
...>   end
...>
...> run(half.(42) >>> half >>> half)
{
  5.25,
  [
    "42 / 2 = 21.0",
    "21.0 / 2 = 10.5",
    "10.5 / 2 = 5.25"
  ]
}

Specs

tell(log()) :: t()

Set the "log" portion of an Algae.Writer step

Examples

iex> tell("secrets")
%Algae.Writer{writer: {%Witchcraft.Unit{}, "secrets"}}

iex> use Witchcraft
...>
...> monad %Algae.Writer{writer: {"string", 1}} do
...>   tell 42
...>   tell 43
...>   return "hey"
...> end
%Algae.Writer{writer: {"hey", 85}}

iex> use Witchcraft
...>
...> half =
...>   fn num ->
...>     monad writer({0.0, ["log"]}) do
...>       let half = num / 2
...>       tell ["#{num} / 2 = #{half}"]
...>       return half
...>     end
...>   end
...>
...> run(half.(42.0) >>> half >>> half)
{
  5.25,
  [
    "42.0 / 2 = 21.0",
    "21.0 / 2 = 10.5",
    "10.5 / 2 = 5.25"
  ]
}

Specs

writer(writer()) :: t()

Similar to new/2, but taking a tuple rather than separate fields.

Examples

iex> writer({"ohai", 42})
%Algae.Writer{writer: {"ohai", 42}}