Powered by AppSignal & Oban Pro

Funx.Monad.Reader

livebooks/monad/reader/reader.livemd

Funx.Monad.Reader

Mix.install([
  {:funx, "0.3.0"}
])

Overview

The Funx.Monad.Reader module represents the Reader monad, which allows computations to access shared, read-only environment values.

This module defines core Reader functions:

  • pure/1 – Lifts a value into the Reader context.
  • run/2 – Executes the Reader with a given environment.
  • asks/1 – Extracts and transforms a value from the environment.
  • ask/0 – Extracts the full environment.
  • tap/2 – Executes a side-effect function on the computed value, returning the original Reader unchanged.

This module implements the following protocol:

  • Funx.Monad: Implements bind/2, map/2, and ap/2 for monadic composition.

Note: The Reader monad does not implement Eq or Ord, since Readers are lazy— they do not actually contain a value until they are run. We only can compare the results of a Reader, not the Reader itself.

Function Examples

import Funx.Monad.Reader
alias Funx.Monad
alias Funx.Monad.Reader
alias Funx.Tappable

Functions

pure/1

Lifts a value into the Reader context.

Examples

reader = pure(42)
run(reader, %{})

Monad.map/2

Applies a function to the computed value inside a Reader monad. The transformation is deferred until the Reader is run.

This function is from the Funx.Monad module, not Funx.Monad.Reader.

Examples

reader =
  pure(42)
  |> Monad.map(&(&1 + 1))

run(reader, %{})
reader =
  asks(&Map.get(&1, :count))
  |> Monad.map(&(&1 * 2))

run(reader, %{count: 10})

Monad.bind/2

Applies a function that returns a Reader to the computed value inside a Reader monad. This is also known as “flatMap” in other languages.

The function receives the computed value and can create a new Reader that may access the same environment.

This function is from the Funx.Monad module, not Funx.Monad.Reader.

Examples

reader =
  pure(42)
  |> Monad.bind(fn x -> pure(div(x, 2)) end)

run(reader, %{})
reader =
  asks(&Map.get(&1, :user_id))
  |> Monad.bind(fn id ->
    asks(fn env ->
      user_name = Map.get(env, :user_name)
      "User #{user_name} has ID #{id}"
    end)
  end)

run(reader, %{user_id: 123, user_name: "Alice"})

Monad.ap/2

Applies a function wrapped in a Reader to a value wrapped in a Reader.

Both Readers share the same environment when run.

This function is from the Funx.Monad module, not Funx.Monad.Reader.

Examples

reader_fn = pure(&(&1 + 1))
reader_val = pure(42)

run(Monad.ap(reader_fn, reader_val), %{})
reader_fn = asks(fn env -> &(&1 + Map.get(env, :offset, 0)) end)
reader_val = pure(10)

run(Monad.ap(reader_fn, reader_val), %{offset: 5})

run/2

Runs the Reader with the provided environment, returning the computed value.

Examples

reader = pure(42)
run(reader, %{})

asks/1

Extracts and transforms the value contained in the environment, making it available within the Reader context.

Examples

reader = asks(fn env -> Map.get(env, :foo) end)
run(reader, %{foo: "bar"})

ask/0

Extracts the value contained in the environment, making it available within the Reader context.

Examples

reader = ask()
run(reader, %{foo: "bar"})

tap/2

Executes a side-effect function on the computed value and returns the original Reader unchanged. The side effect is deferred - it executes when the Reader is run, not when tap is called.

Useful for debugging, logging, or performing side effects based on environment data.

Examples

# Side effect on computed value (deferred until run)
reader =
  pure(42)
  |> Tappable.tap(&IO.inspect(&1, label: "value"))

run(reader, %{})
# With environment access
reader =
  asks(&Map.get(&1, :user_id))
  |> Tappable.tap(fn id -> IO.puts("Processing user: #{id}") end)

run(reader, %{user_id: 123})