lib/monad/effect/usage-rules.md

# `Funx.Monad.Effect` Usage Rules

## Quick Navigation Index

- **Construction**: `right/2`, `left/2`, `lift_func/2`, `lift_either/2`, `lift_maybe/3`
- **Composition**: `map/2`, `bind/2`, `ap/2`, `traverse/2`, `traverse_a/2`
- **Execution**: `run/1`, `run/2`, `Context`, `Task.Supervisor` integration
- **Validation**: `validate/2`, error accumulation patterns
- **Observability**: `span_name`, telemetry events, trace hierarchies

## LLM Functional Programming Foundation

**Key Concepts for LLMs:**

**CRITICAL Elixir Implementation**: All monadic operations are under `Funx.Monad` protocol

- **NO separate Functor/Applicative protocols** - Elixir protocols cannot be extended after definition
- **Use `import Funx.Monad`** for access to `map/2`, `bind/2`, `ap/2` - protocol-based, not macros
- **Avoid `use Funx.Monad`** - Effect composition works via protocol dispatch, not macro injection
- Different from Haskell's separate Functor, Applicative, Monad typeclasses

**Effect**: A deferred computation description that may succeed (`Right`) or fail (`Left`)

- **Pure descriptions**: Effects are pure instructions, not computations - execution is deferred
- **Concurrent by default**: Unlike ZIO, Effect runs concurrently - challenge is sequential control  
- **Controlled execution boundary**: `run/2` provides timeouts, isolation, and telemetry
- **Reader integration**: Built-in environment access for dependency injection
- **Exception-safe**: Automatically wraps exceptions in structured `EffectError`

**Theoretical Foundation**: Based on Philip Wadler's 1995 solution to the I/O problem

- **The Problem**: Side effects break referential transparency in functional programming
- **Wadler's Solution**: Model effects as pure instructions, defer execution to controlled boundary
- **Key Insight**: Instructions for producing side effects can remain pure even when effects themselves are impure
- **Effect Implementation**: Pure descriptions of computations + controlled execution in `run/2`
- **Preserves FP**: Maintains composability and reasoning while enabling real-world I/O

**Elixir-Specific Design**: Unlike most Effect libraries, Funx.Effect has **two distinct structs**

- **`Effect.Right`**: Describes a computation intended to succeed
- **`Effect.Left`**: Describes a computation intended to fail  
- **Pattern matching friendly**: Can match on Effect structure before execution
- **Structural short-circuiting**: `Effect.Left` detected during traversal—no task scheduled if Left matched early
- **Implementation quirk**: Makes functional composition cleaner in Elixir's pattern-matching environment

**Kleisli Function**: A function `a -> Effect b` (takes unwrapped value, returns wrapped Effect)

- **Primary use**: `traverse/2` and `traverse_a/2` for list operations  
- **Individual use**: `Monad.bind/2` for single Effect values
- **Context-aware**: Propagates trace context through execution
- Example: `fetch_user :: UserId -> Effect User`

**Key List Operation Patterns:**

- `sequence([Effect a])` → `Effect [a]` (fail-fast, sequential execution)
- `sequence_a([Effect a])` → `Effect [a]` (parallel execution, collect all errors)
- `traverse([a], kleisli_fn)` → `Effect [b]` (sequential: apply Kleisli, fail-fast)
- `traverse_a([a], kleisli_fn)` → `Effect [b]` (parallel: apply Kleisli, accumulate errors)

**Performance Critical**: `bind` chains run sequentially, `traverse_a` runs in parallel

**Functor**: Something you can `map` over while preserving structure

- `Monad.map/2 :: (a -> b) -> Effect a -> Effect b`
- Transforms the success value, leaves Left unchanged, preserves async structure

**Applicative**: Allows applying functions inside a context

- `Monad.ap/2 :: Effect (a -> b) -> Effect a -> Effect b`  
- Can combine multiple Effect values with proper trace context merging

**Monad**: Supports `bind` for chaining dependent computations

- `Monad.bind/2 :: Effect a -> (a -> Effect b) -> Effect b`
- Flattens nested Effect values automatically, maintains trace lineage

**Reader Pattern**: Access to runtime environment within effects

- `ask/0` - Returns environment as Right
- `asks/1` - Applies function to environment, returns result as Right
- `fail/0` - Returns environment as Left (failure mode)
- `fails/1` - Applies function to environment, returns result as Left

**Dependency Injection**: Inject behavior, not configuration

- Environment provides implementations: `%{store: MyStore, logger: MyLogger}`
- Effects remain decoupled from specific implementations
- Enables evolutionary design - defer architectural decisions safely

**Monad Relationships**: Effect combines familiar monadic patterns

- **Effect ≈ Reader + Either + Async**: Reads environment, produces Either results, defers execution
- **Reader integration**: `ask/0`, `asks/1`, `fail/0`, `fails/1` for environment access
- **Either foundation**: Operations return `Either.Right`/`Either.Left` when executed  
- **Async deferred**: Unlike Reader/Either, execution is deferred until `run/2`
- **Mathematical**: `Effect env a ≈ env -> Task (Either error a)` (execution semantics only)
- **Key difference**: Unlike `Task`, Effect descriptions are inert until `run/2`—nothing scheduled until execution

**Context & Observability**:

- Every Effect carries `Effect.Context` with trace_id, span_name, timeout
- Automatic telemetry emission on `run/2` with `[:funx, :effect, :run, :start/:stop]`
- Spans are linked hierarchically through parent_trace_id
- Exception handling wraps all errors in structured `EffectError`

## LLM Decision Guide: When to Use Effect

**✅ Use Effect when:**

- Deferred asynchronous computation (database calls, HTTP requests, file I/O)
- Need full observability and tracing in concurrent systems  
- Complex workflows requiring both fail-fast and error accumulation
- Integration with Task.Supervisor for fault tolerance
- Reader-style dependency injection with environment access
- User says: "async", "concurrent", "observable", "traced", "supervised"

**❌ Use Either when:**

- Synchronous operations that don't need deferral
- Simple error handling without telemetry overhead
- No need for tracing or span management
- User says: "simple validation", "immediate result", "no async"

**❌ Use Maybe when:**

- Simple presence/absence without error context
- No async requirements
- User says: "optional", "might not exist"

**⚡ Effect Strategy Decision:**

- **Simple async operation**: Use `right/1` and `left/1` constructors
- **Chain dependent async operations**: Use `bind/2` for Effect sequencing
- **Transform success values**: Use `map/2` with regular functions  
- **Combine multiple Effects**: Use `ap/2` for applicative patterns
- **Environment access**: Use `ask/0`, `asks/1`, `fail/0`, `fails/1`
- **List processing (fail-fast)**: Use `traverse/2` and `sequence/1`
- **List processing (parallel, accumulate errors)**: Use `traverse_a/2` and `sequence_a/1`
- **Validation with error accumulation**: Use `validate/2`
- **Performance optimization**: `bind` cheap checks before expensive ones
- **Exception-safe lifting**: Use `lift_func/2`, `lift_either/2`, etc.

**⚙️ Function Choice Guide (Mathematical Purpose):**

- **Chain dependent async lookups**: `bind/2` with functions returning Effect
- **Transform success values**: `map/2` with functions returning plain values
- **Apply functions to multiple Effects**: `ap/2` for combining contexts
- **Access runtime environment**: `ask/0` for full env, `asks/1` with selector
- **Fail with environment context**: `fail/0` or `fails/1`
- **Work with lists (fail-fast)**: `sequence/1`, `traverse/2`
- **Work with lists (collect errors)**: `sequence_a/1`, `traverse_a/2`
- **Lift synchronous operations**: `lift_func/2`, `lift_predicate/3`
- **Convert from other types**: `lift_either/2`, `lift_maybe/3`

## LLM Context Mapping

**User Intent → Effect Patterns:**

- "fetch user then profile" → `bind/2` chaining dependent operations
- "combine multiple API calls" → `traverse_a/2` for parallel processing  
- "validate with all errors" → `validate/2` with multiple validators
- "trace this operation" → Add `span_name` to context
- "handle database errors" → `map_left/2` for error transformation
- "access config in effect" → `asks/1` to read from environment
- "process list async" → `traverse_a/2` for error accumulation
- "async database call" → `lift_func/2` wrapping database operation
- "supervised task execution" → Pass `:task_supervisor` to `run/2`

## Syntax Patterns

- **Construction**: `Effect.right(value)`, `Effect.left(error)`
- **Composition**: `import Funx.Monad` then `|> bind(fn x -> ... end)`
- **Execution**: **Always call `run/2`** - Effects are lazy until executed
- **Environment**: `asks/1` for dependency injection, `run/2` provides environment
- **Error handling**: All exceptions wrapped in `EffectError`, use `map_left/2` for transformation
- **Tracing**: `span_name` creates hierarchical spans, context bound at creation

## Overview

`Funx.Monad.Effect` handles deferred asynchronous computations with full observability.

Use Effect for:

- Asynchronous operations (database, HTTP, file I/O)
- Complex workflows requiring tracing and telemetry  
- Error accumulation across multiple async operations
- Reader-style dependency injection with environment access
- Integration with Task.Supervisor for fault tolerance

**Key insight**: Effect represents **"deferred observable async computation"** - build up a pure description of what to do, then `run/2` executes it with full observability and exception safety.

**Remember**: Effects are pure until executed - this preserves functional programming benefits while enabling real-world I/O.

## Constructors

### `right/2` - Describe a Computation Intended to Succeed

Creates an `Effect.Right` struct describing a computation that should succeed:

```elixir
Effect.right(42)                    # Creates %Effect.Right{} - success intent
Effect.right(42, span_name: "calc") # With tracing context
```

### `left/2` - Describe a Computation Intended to Fail  

Creates an `Effect.Left` struct describing a computation that should fail:

```elixir
Effect.left("error")                      # Creates %Effect.Left{} - failure intent
Effect.left("error", span_name: "fail")  # With tracing context
```

**Key insight**: These create **different struct types** (`Effect.Right` vs `Effect.Left`), not the same struct with different content. This enables pattern matching on Effect structure before execution.

### `pure/2` - Alias for `right/2`

Alternative constructor for successful effects (Applicative identity):

```elixir
Effect.pure(42)                     # Same as Effect.right(42) - creates Effect.Right
Effect.pure(42, trace_id: "xyz")   # With custom trace context
```

**Note**: `pure/2` does not change concurrency or evaluation semantics—it creates an `Effect.Right` struct. Use in applicative patterns where you need the identity element for composition.

## Execution

### `run/1` - Execute the Effect

Executes the deferred computation and returns an Either result:

```elixir
import Funx.Monad

Effect.right(42)
|> Effect.run()                     # %Either.Right{right: 42}

Effect.left("error")  
|> Effect.run()                     # %Either.Left{left: "error"}
```

### `run/2` - Execute with Environment

Passes runtime environment to the effect:

```elixir
Effect.asks(fn env -> env[:user_id] end)
|> Effect.run(%{user_id: 123})     # %Either.Right{right: 123}
```

### `run/3` - Execute with Environment and Options

Supports additional execution options:

```elixir
{:ok, supervisor} = Task.Supervisor.start_link()

Effect.right(42)
|> Effect.run(%{}, task_supervisor: supervisor, span_name: "supervised")
```

## Core Operations

### `map/2` - Transform Success Values

Applies a function to the success value inside an Effect:

```elixir
import Funx.Monad

Effect.right(5)
|> map(fn x -> x * 2 end)
|> Effect.run()                     # right(10)

Effect.left("error")
|> map(fn x -> x * 2 end)
|> Effect.run()                     # left("error") - function never runs
```

**Use `map` when:**

- You want to transform the success value if present
- The transformation function returns a plain value (not wrapped in Effect)
- You want to preserve the Effect structure and async nature

### `bind/2` - Chain Dependent Async Operations

Chains operations that return Effect values, automatically flattening:

```elixir
import Funx.Monad

# These functions return Effect values  
fetch_user = fn id -> 
  Effect.lift_func(fn -> Database.get_user(id) end) 
end

fetch_profile = fn user -> 
  Effect.lift_func(fn -> Database.get_profile(user.id) end)
end

Effect.right(123)
|> bind(fetch_user)           # Effect User
|> bind(fetch_profile)        # Effect Profile  
|> Effect.run(env)
```

**Use `bind` when:**

- You're chaining async operations that each return Effect
- Each step depends on the result of the previous async step
- You want automatic short-circuiting on Left with trace preservation
- You need sequential execution (each operation waits for the previous)

**Common bind pattern (sequential execution):**

```elixir
def process_user_workflow(user_id, env) do
  Effect.right(user_id)
  |> bind(&fetch_user_async/1)       # UserId -> Effect User
  |> bind(&fetch_permissions_async/1) # User -> Effect Permissions
  |> bind(&validate_access_async/1)   # Permissions -> Effect AccessToken
  |> Effect.run(env)
end
```

### `ap/2` - Apply Functions Across Effect Values

Applies a function in an Effect to a value in an Effect:

```elixir
import Funx.Monad

# Apply a wrapped function to wrapped values
Effect.right(fn x -> x + 10 end)
|> ap(Effect.right(5))
|> Effect.run()                     # right(15)

# Combine multiple Effect values  
add = fn x -> fn y -> x + y end end

Effect.right(add)
|> ap(Effect.right(3))              # Effect(fn y -> 3 + y end)
|> ap(Effect.right(4))              # Effect(7)
|> Effect.run()                     # right(7)

# If any value is Left, result is Left
Effect.right(add)
|> ap(Effect.left("error"))         
|> ap(Effect.right(4))
|> Effect.run()                     # left("error")
```

**Use `ap` when:**

- You want to apply a function to multiple Effect values
- You need all async operations to complete for the computation to succeed
- You're implementing applicative patterns with trace context preservation

**Concurrency note**: `ap/2` enables applicative composition. Provided both arguments are constructed independently, effects may run concurrently. However, if Effects are constructed in dependency chains, they will run sequentially.

## Reader Operations

### `ask/0` - Access Full Environment

Returns the runtime environment passed to `run/2` as a Right:

```elixir
Effect.ask()
|> map(fn env -> env[:database_url] end)
|> Effect.run(%{database_url: "postgres://..."})  # right("postgres://...")
```

### `asks/1` - Extract from Environment  

Applies a function to extract specific values from the environment:

```elixir
Effect.asks(fn env -> env[:config][:timeout] end)
|> Effect.run(%{config: %{timeout: 5000}})        # right(5000)
```

### `fail/0` - Fail with Full Environment

Returns the runtime environment as a Left (failure case):

```elixir
Effect.fail()
|> Effect.run(%{error: :invalid_token})           # left(%{error: :invalid_token})
```

### `fails/1` - Fail with Processed Environment

Applies a function to the environment and returns result as Left:

```elixir
Effect.fails(fn env -> {:unauthorized, env[:user_id]} end)
|> Effect.run(%{user_id: 42})                     # left({:unauthorized, 42})
```

**Reader Pattern Usage:**

```elixir
def fetch_with_config do
  Effect.asks(fn env -> {env[:api_base], env[:auth_token]} end)
  |> bind(fn {base, token} ->
    Effect.lift_func(fn -> HTTPClient.get("#{base}/users", headers: [{"auth", token}]) end)
  end)
end
```

**Understanding the Monad Stack:**

```elixir
# Effect combines three familiar patterns:

# 1. Reader: Environment access
Effect.asks(fn env -> env[:database_config] end)

# 2. Either: Success/failure handling  
|> bind(fn config ->
  case Database.connect(config) do
    {:ok, conn} -> Effect.right(conn)      # Success path
    {:error, reason} -> Effect.left(reason) # Failure path
  end
end)

# 3. Async: Deferred execution until run/2
|> Effect.run(%{database_config: config})  # Returns Either.Right or Either.Left

# Mathematically: Effect env a ≈ env -> Task (Either error a)
```

## Context & Observability

### Effect.Context - Tracing and Telemetry

Every Effect carries context for observability:

```elixir
# Create context with span name and timeout
context = Effect.Context.new(
  span_name: "fetch_user_data",
  timeout: 10_000,
  trace_id: "custom-trace-id"
)

Effect.right(user_id, context)
|> bind(&fetch_user_async/1)
|> Effect.run(env)

# Telemetry events emitted:
# [:funx, :effect, :run, :start] - when execution begins  
# [:funx, :effect, :run, :stop]  - when execution completes
```

### Span Naming and Hierarchies

Effects automatically create hierarchical spans:

```elixir
# Parent effect
parent_effect = Effect.right(42, span_name: "parent_operation")

# Child operations create nested spans
result = parent_effect
|> bind(fn x -> 
  Effect.right(x * 2, span_name: "double_value")  # Creates "bind -> parent_operation"
end)
|> map(fn x -> x + 1)  # Creates "map -> bind -> parent_operation"
|> Effect.run(env, span_name: "execute")  # Promotes to "execute -> map -> bind -> parent_operation"
```

### Telemetry Events

Effect automatically emits structured telemetry events for observability:

**Core Events:**

```elixir
# When Effect execution begins
[:funx, :effect, :run, :start]

# When Effect execution completes
[:funx, :effect, :run, :stop]
```

**Event Metadata:**

```elixir
%{
  span_name: "user_operation",        # Current span name
  trace_id: "abc123...",             # Unique trace identifier  
  parent_trace_id: "def456...",      # Parent span if nested
  timeout: 5000,                     # Configured timeout
  # Plus any custom metadata from Context
}
```

**Measurements:**

```elixir
%{
  duration: 1_234_567,  # Execution time in nanoseconds (for :stop events)
  count: 1              # Always 1 for Effect executions
}
```

**Example telemetry handler:**

```elixir
:telemetry.attach_many(
  "effect-observer",
  [[:funx, :effect, :run, :start], [:funx, :effect, :run, :stop]],
  fn event, measurements, metadata, _config ->
    case event do
      [:funx, :effect, :run, :start] ->
        Logger.info("Effect started: #{metadata.span_name}")
        
      [:funx, :effect, :run, :stop] ->
        duration_ms = measurements.duration / 1_000_000
        Logger.info("Effect completed: #{metadata.span_name} in #{duration_ms}ms")
    end
  end,
  nil
)
```

### Exception Handling

All exceptions are automatically wrapped in `EffectError`:

```elixir
Effect.lift_func(fn -> 1 / 0 end)  
|> Effect.run()
# Returns: left(%EffectError{stage: :lift_func, reason: %ArithmeticError{}})

Effect.right(42)
|> map(fn _ -> raise "boom" end)
|> Effect.run()  
# Returns: left(%EffectError{stage: :map, reason: %RuntimeError{message: "boom"}})
```

**EffectError Structure:**

```elixir
%Funx.Errors.EffectError{
  stage: atom(),    # Where error occurred: :lift_func, :map, :bind, :ap, :run
  reason: any()     # Original exception or error reason
}
```

**Common EffectError stages:**

- `:lift_func` - Exception in lifted function
- `:map` - Exception in map transformation  
- `:bind` - Exception in bind function
- `:ap` - Exception in applicative function
- `:run` - Timeout or task execution failure
- `:lift_either` - Exception in Either-returning function

## List Operations

### `sequence/1` - Fail-Fast Processing

Processes a list of Effects, stopping at the first Left:

```elixir
effects = [
  Effect.right(1, span_name: "first"),
  Effect.right(2, span_name: "second"), 
  Effect.right(3, span_name: "third")
]

Effect.sequence(effects)
|> Effect.run()                     # right([1, 2, 3])

# With failure - stops at first Left (pattern matching optimization)
effects_with_error = [
  Effect.right(1),
  Effect.left("error"),    # %Effect.Left{} - can short-circuit here
  Effect.right(3)          # Never executes because Left found
]

Effect.sequence(effects_with_error)
|> Effect.run()                     # left("error")
```

**Elixir-specific optimization**: Because `Effect.left/2` creates an `%Effect.Left{}` struct, traversals can pattern match for structural short-circuiting—no task is scheduled if a Left is detected early during traversal construction.

### `sequence_a/1` - Error Accumulation

Processes all Effects and accumulates any errors:

```elixir
effects_with_errors = [
  Effect.right(1),
  Effect.left("Error 1"),
  Effect.left("Error 2"), 
  Effect.right(4)
]

Effect.sequence_a(effects_with_errors)
|> Effect.run()                     # left(["Error 1", "Error 2"])

# All succeed
all_success = [Effect.right(1), Effect.right(2), Effect.right(3)]

Effect.sequence_a(all_success)
|> Effect.run()                     # right([1, 2, 3])
```

### `traverse/2` - Apply Kleisli Function (Fail-Fast)

Applies a Kleisli function to each element, stopping at first failure:

```elixir
validate_positive = fn n ->
  Effect.lift_predicate(n, &(&1 > 0), fn x -> "#{x} is not positive" end)
end

Effect.traverse([1, 2, 3], validate_positive)
|> Effect.run()                     # right([1, 2, 3])

Effect.traverse([1, -2, 3], validate_positive)  
|> Effect.run()                     # left("-2 is not positive")
```

### `traverse_a/2` - Apply Kleisli Function (Accumulate Errors)

Applies a Kleisli function to each element, collecting all errors:

```elixir
Effect.traverse_a([1, -2, -3], validate_positive)
|> Effect.run()                     # left(["-2 is not positive", "-3 is not positive"])
```

**Use `traverse` vs `traverse_a`:**

- **traverse**: When you need all operations to succeed (fail-fast, sequential)
- **traverse_a**: When you want to see all validation errors (accumulate, parallel)

**Key Performance Difference**: `traverse_a` runs all operations concurrently, `traverse` stops at first failure.

### `validate/2` - Multi-Validator Error Accumulation  

Validates a value using multiple validator functions:

```elixir
validate_positive = fn x ->
  Effect.lift_predicate(x, &(&1 > 0), fn n -> "#{n} must be positive" end)
end

validate_even = fn x ->
  Effect.lift_predicate(x, &(rem(&1, 2) == 0), fn n -> "#{n} must be even" end)
end

# All validators pass
Effect.validate(4, [validate_positive, validate_even])
|> Effect.run()                     # right(4)

# Multiple validators fail - accumulates errors
Effect.validate(-3, [validate_positive, validate_even])  
|> Effect.run()                     # left(["-3 must be positive", "-3 must be even"])
```

## Lifting Operations

### `lift_func/2` - Lift Synchronous Function

Lifts a zero-arity function into an Effect, executing it asynchronously:

```elixir
Effect.lift_func(fn -> expensive_computation() end)
|> Effect.run()                     # Runs async, returns right(result)

# Exception handling
Effect.lift_func(fn -> raise "boom" end)
|> Effect.run()                     # left(%EffectError{stage: :lift_func, reason: %RuntimeError{}})
```

### `lift_either/2` - Lift Either-Returning Function

Lifts a function that returns Either into an Effect:

```elixir
Effect.lift_either(fn -> validate_email("user@example.com") end)
|> Effect.run()                     # Defers Either evaluation until run
```

### `lift_maybe/3` - Lift Maybe with Fallback

Converts a Maybe into an Effect with error fallback:

```elixir
maybe_user = Maybe.just(%User{id: 1})

Effect.lift_maybe(maybe_user, fn -> "User not found" end)
|> Effect.run()                     # right(%User{id: 1})

Effect.lift_maybe(Maybe.nothing(), fn -> "User not found" end)  
|> Effect.run()                     # left("User not found")
```

### `lift_predicate/3` - Lift Predicate Check

Lifts a predicate validation into an Effect:

```elixir
Effect.lift_predicate(10, &(&1 > 5), fn x -> "#{x} too small" end)
|> Effect.run()                     # right(10)

Effect.lift_predicate(3, &(&1 > 5), fn x -> "#{x} too small" end)
|> Effect.run()                     # left("3 too small")
```

## Error Handling

### `map_left/2` - Transform Left Values

Transforms error values while leaving Right unchanged:

```elixir
Effect.left("simple error")
|> Effect.map_left(fn e -> %{error: e, code: 400} end)
|> Effect.run()                     # left(%{error: "simple error", code: 400})

Effect.right(42)
|> Effect.map_left(fn _ -> "never called" end)
|> Effect.run()                     # right(42)
```

### `flip_either/1` - Invert Success/Failure

Swaps Right and Left values:

```elixir
Effect.flip_either(Effect.right("success"))
|> Effect.run()                     # left("success")

Effect.flip_either(Effect.left("error"))  
|> Effect.run()                     # right("error")
```

## Common Patterns

### Async Pipeline with Error Handling

```elixir
def process_user_registration(email, password, env) do
  Effect.right({email, password})
  |> bind(fn {e, p} -> validate_email_async(e) |> map(fn _ -> {e, p} end) end)
  |> bind(fn {e, p} -> hash_password_async(p) |> map(fn h -> {e, h} end) end)
  |> bind(fn {e, h} -> create_user_async(e, h) end)
  |> bind(&send_welcome_email_async/1)
  |> Effect.run(env)
end
```

### Parallel API Calls with Error Accumulation

```elixir  
def fetch_dashboard_data(user_id, env) do
  api_calls = [
    fetch_user_profile(user_id),
    fetch_recent_orders(user_id),  
    fetch_recommendations(user_id)
  ]
  
  Effect.sequence_a(api_calls, span_name: "dashboard_data")
  |> map(fn [profile, orders, recs] -> 
    %{profile: profile, orders: orders, recommendations: recs}
  end)
  |> Effect.run(env)
end
```

### Performance-Optimized Validation Pipeline

```elixir
def validate_ride_access(patron, ride, env) do
  # Fast local checks first (milliseconds)
  Effect.right(patron)
  |> bind(&validate_age_height/1)
  |> bind(&validate_ticket_tier/1)
  |> bind(fn patron ->
    # Only do expensive I/O checks for eligible patrons (500ms)
    check_ride_maintenance_status(ride, env)
    |> map(fn _ -> patron end)
  end)
  |> Effect.run(env)
end

# bind chains: Sequential execution, short-circuit on first failure  
# traverse_a: Parallel execution, collect all errors
```

### Dependency Injection with Reader

```elixir
# Effect stays decoupled from specific implementations
def save_user_data(user, env) do
  Effect.asks(fn e -> {e[:store], e[:logger]} end)
  |> bind(fn {store, logger} ->
    Effect.lift_func(fn -> logger.info("Saving user #{user.id}") end)
    |> bind(fn _ -> store.save(user) end)
  end)  
  |> Effect.run(env)
end

# Runtime injection enables evolutionary design
dev_env = %{store: InMemoryStore, logger: ConsoleLogger}
prod_env = %{store: PostgreSQLStore, logger: TelemetryLogger}
```

### Timeout and Supervision

```elixir
{:ok, sup} = Task.Supervisor.start_link()

context = Effect.Context.new(
  span_name: "long_running_task",
  timeout: 30_000
)

Effect.lift_func(fn -> very_expensive_operation() end, context)
|> Effect.run(%{}, task_supervisor: sup)
```

## Elixir Interoperability

### `from_result/2` - Convert Result Tuples

```elixir
Effect.from_result({:ok, 42})
|> Effect.run()                     # right(42)

Effect.from_result({:error, "failed"})
|> Effect.run()                     # left("failed")
```

### `to_result/1` - Convert to Result Tuples

```elixir
Effect.to_result(Effect.right(42))              # {:ok, 42}
Effect.to_result(Effect.left("error"))          # {:error, "error"}
```

### `from_try/2` - Exception-Safe Kleisli

Creates a Kleisli function that catches exceptions:

```elixir
safe_div = Effect.from_try(fn x -> 10 / x end)

Effect.right(2)
|> bind(safe_div)
|> Effect.run()                     # right(5.0)

Effect.right(0)  
|> bind(safe_div)
|> Effect.run()                     # left(%ArithmeticError{})
```

### `to_try!/1` - Extract or Raise

```elixir
Effect.to_try!(Effect.right(42))                # 42

Effect.to_try!(Effect.left(%RuntimeError{message: "boom"}))  
# raises RuntimeError: "boom"
```

## Testing Strategies

### Property-Based Testing

```elixir
defmodule EffectPropertyTest do
  use ExUnit.Case
  use StreamData
  import Funx.Monad

  property "map preserves Right structure" do
    check all value <- term(),
              f <- StreamData.constant(fn x -> x + 1 end) do
      result = Effect.right(value) |> map(f) |> Effect.run()
      assert match?(%Either.Right{}, result)
    end
  end

  property "bind chains preserve trace context" do
    check all value <- integer() do
      effect = Effect.right(value, span_name: "test")
      |> bind(fn x -> Effect.right(x * 2, span_name: "double") end)
      
      result = Effect.run(effect)
      assert match?(%Either.Right{right: doubled}, result) when doubled == value * 2
    end
  end
end
```

### Unit Testing with Telemetry

```elixir
defmodule EffectTest do
  use ExUnit.Case
  import Funx.Monad

  setup do
    :telemetry.attach_many(
      "test-handler",
      [[:funx, :effect, :run, :start], [:funx, :effect, :run, :stop]],
      fn event, measurements, metadata, _ ->
        send(self(), {:telemetry, event, measurements, metadata})
      end,
      nil
    )
    
    on_exit(fn -> :telemetry.detach("test-handler") end)
  end

  test "async pipeline emits correct telemetry" do
    result = Effect.right(10, span_name: "start")
    |> bind(fn x -> Effect.right(x * 2, span_name: "double") end)
    |> Effect.run()

    assert result == Either.right(20)
    
    assert_received {:telemetry, [:funx, :effect, :run, :stop], _, %{span_name: "start"}}
    assert_received {:telemetry, [:funx, :effect, :run, :stop], _, %{span_name: "double"}}  
    assert_received {:telemetry, [:funx, :effect, :run, :stop], _, %{span_name: "bind -> start"}}
  end

  test "error accumulation in traverse_a" do
    validator = fn x ->
      if x > 0, do: Effect.right(x), else: Effect.left("negative: #{x}")
    end
    
    result = Effect.traverse_a([1, -2, 3, -4], validator) |> Effect.run()
    
    assert result == Either.left(["negative: -2", "negative: -4"])
  end
end
```

## Performance Considerations

### Sequential vs Parallel Execution Patterns

```elixir
# ❌ Sequential: 1.5 seconds total (3 × 500ms each)
def check_maintenance_sequential(ride) do
  Effect.right(ride)
  |> bind(&check_scheduled_maintenance/1)  # 500ms
  |> bind(&check_unscheduled_maintenance/1) # 500ms  
  |> bind(&check_compliance_hold/1)        # 500ms
  |> Effect.run()
end

# ✅ Parallel: 500ms total (all run concurrently)  
def check_maintenance_parallel(ride) do
  checks = [
    check_scheduled_maintenance(ride),     # 500ms concurrent
    check_unscheduled_maintenance(ride),   # 500ms concurrent
    check_compliance_hold(ride)            # 500ms concurrent
  ]
  
  Effect.sequence_a(checks) |> Effect.run()  # All run in parallel
end

# Rule: Use bind for dependent operations, traverse_a for independent operations
```

### Smart Performance Optimization

```elixir
# ✅ Fast eligibility checks before expensive I/O
def validate_access_optimized(patron, ride) do
  Effect.right(patron)
  # Fast local validations first (1-2ms total)
  |> bind(&validate_age/1)
  |> bind(&validate_height/1) 
  |> bind(&validate_ticket/1)
  # Only do expensive I/O for eligible patrons (500ms)
  |> bind(fn _ -> check_ride_online_status(ride) end)
end

# Best case: Ineligible patron rejected in 2ms
# Worst case: Eligible patron checked in 502ms
```

### Context Propagation Overhead

```elixir
# Context merging and span creation has minimal overhead
# But consider span naming strategy for high-volume operations

# Good: Generic span names for repeated operations
Effect.lift_func(fn -> process_item(item) end, span_name: "process_item")

# Avoid: Unique span names that create too many distinct spans  
# Effect.lift_func(fn -> process_item(item) end, span_name: "process_item_#{item.id}")
```

### Memory Usage

```elixir
# Effects are lightweight until executed
# Deferred nature means no computation until run/2

# Efficient for conditional execution
user_effect = if admin_user? do
  Effect.lift_func(fn -> expensive_admin_operation() end)
else
  Effect.right(:skip)
end

# Only runs expensive operation if needed
Effect.run(user_effect)
```

## Troubleshooting Common Issues

### Issue: Forgetting to Call `run/2`

```elixir
# ❌ Problem: Effect never executes
effect = Effect.right(42)
# Missing Effect.run(effect) - nothing happens!

# ✅ Solution: Always call run to execute
result = Effect.right(42) |> Effect.run()
```

### Issue: Nested Effect Values

```elixir
# ❌ Problem: Manual nesting creates Effect Effect a
result = Effect.right(user_id)
|> map(&fetch_user_effect/1)  # fetch_user_effect returns Effect User  
# Result: Effect (Effect User) - nested!

# ✅ Solution: Use bind for functions that return Effect
result = Effect.right(user_id)
|> bind(&fetch_user_effect/1)  # Automatically flattens to Effect User
```

### Issue: Blocking on Async Operations

```elixir
# ❌ Problem: Sequential execution loses concurrency benefits
def fetch_data_slow(ids) do
  Enum.reduce(ids, Effect.right([]), fn id, acc ->
    acc |> bind(fn results ->
      fetch_item(id) |> map(fn item -> [item | results] end)
    end)
  end)
end

# ✅ Solution: Use traverse_a for concurrent processing
def fetch_data_fast(ids) do
  Effect.traverse_a(ids, &fetch_item/1)
end
```

### Issue: Losing Error Context

```elixir
# ❌ Problem: Generic error handling loses specifics
Effect.lift_func(fn -> Database.query("SELECT * FROM users") end)
|> map_left(fn _ -> "database error" end)  # Lost original error details

# ✅ Solution: Preserve error context  
Effect.lift_func(fn -> Database.query("SELECT * FROM users") end)
|> map_left(fn 
  %EffectError{reason: %DBConnection.Error{} = db_err} -> 
    %{error: :database, details: db_err, operation: :fetch_users}
  error -> 
    %{error: :unknown, details: error, operation: :fetch_users}
end)
```

### Issue: Trace Context Confusion

```elixir
# ❌ Problem: Inconsistent span naming makes tracing hard to follow
Effect.right(data, span_name: "a") 
|> bind(fn x -> Effect.right(process(x), span_name: "x") end)
|> map(fn y -> transform(y))  # No span name context lost

# ✅ Solution: Consistent span naming strategy
Effect.right(data, span_name: "load_user_data")
|> bind(fn x -> Effect.right(process(x), span_name: "validate_user_data") end)  
|> map(fn y -> transform(y))  # Inherits "map -> validate_user_data"
```

## When Not to Use Effect

### Use Either Instead When

```elixir
# ❌ Effect overhead for simple sync validation
def validate_email_sync(email) do
  Effect.lift_predicate(email, &valid_email_format?/1, fn _ -> "invalid email" end)
  |> Effect.run()
end

# ✅ Either for immediate sync operations  
def validate_email_sync(email) do
  if valid_email_format?(email) do
    Either.right(email)
  else
    Either.left("invalid email")
  end
end
```

### Use OTP/GenServer Instead for Long-Running Tasks

```elixir
# ❌ Effect for persistent background workers
def start_queue_processor do
  Effect.lift_func(fn ->
    spawn(fn -> 
      # This runs forever - Effect is wrong abstraction
      continuously_process_queue()
    end)
  end)
  |> Effect.run()
end

# ✅ GenServer for stateful, long-running services
defmodule QueueProcessor do
  use GenServer

  def start_link(opts) do
    GenServer.start_link(__MODULE__, opts, name: __MODULE__)
  end

  def init(_opts) do
    schedule_work()
    {:ok, %{processed: 0}}
  end

  def handle_info(:work, state) do
    process_next_item()
    schedule_work()
    {:noreply, %{state | processed: state.processed + 1}}
  end

  defp schedule_work do
    Process.send_after(self(), :work, 1000)
  end
end
```

### Use Job Queues Instead for High-Volume Processing

```elixir
# ❌ Effect for job queues - no back-pressure or persistence
def process_user_sign_ups(user_ids) do
  Effect.traverse_a(user_ids, fn id ->
    Effect.lift_func(fn -> send_welcome_email(id) end)
  end)
  |> Effect.run()
end

# ✅ Oban for reliable job processing with back-pressure
defmodule WelcomeEmailWorker do
  use Oban.Worker, queue: :emails, max_attempts: 3

  @impl Oban.Worker
  def perform(%Oban.Job{args: %{"user_id" => user_id}}) do
    send_welcome_email(user_id)
    :ok
  end
end

# Enqueue jobs with back-pressure and persistence
def process_user_sign_ups(user_ids) do
  jobs = Enum.map(user_ids, fn id ->
    WelcomeEmailWorker.new(%{user_id: id})
  end)
  
  Oban.insert_all(jobs)
end
```

### Use Streaming Libraries Instead for Data Pipelines

```elixir
# ❌ Effect for large data processing - memory issues
def process_large_dataset(data) do
  Effect.traverse_a(data, fn item ->
    Effect.lift_func(fn -> expensive_transformation(item) end)
  end)
  |> Effect.run()
end

# ✅ Flow for back-pressured stream processing
def process_large_dataset(data) do
  data
  |> Flow.from_enumerable(max_demand: 100)
  |> Flow.map(&expensive_transformation/1)
  |> Flow.partition()
  |> Flow.reduce(fn -> [] end, fn item, acc -> [item | acc] end)
  |> Enum.to_list()
end

# ✅ Broadway for robust event processing
defmodule DataProcessor do
  use Broadway

  def start_link(_opts) do
    Broadway.start_link(__MODULE__,
      name: __MODULE__,
      producer: [
        module: {BroadwayRabbitMQ.Producer, queue: "data_queue"}
      ],
      processors: [
        default: [concurrency: 10]
      ]
    )
  end

  def handle_message(_processor, message, _context) do
    message
    |> Message.update_data(&expensive_transformation/1)
  end
end
```

### But Effect Enables Evolutionary Design

```elixir
# Start simple - Effect allows architectural evolution
def check_user_status(user_id) do
  # Initially: simple boolean check
  Effect.right(user_id > 0)
  |> Effect.run()
end

# Later: evolve to database lookup without changing interface  
def check_user_status(user_id) do
  Effect.asks(fn env -> env[:user_store] end)
  |> bind(fn store -> store.get_user_status(user_id) end)
  |> Effect.run(env)
end

# Finally: evolve to complex validation with multiple services
def check_user_status(user_id) do
  status_checks = [
    check_account_standing(user_id),
    check_payment_status(user_id), 
    check_compliance_status(user_id)
  ]
  
  Effect.sequence_a(status_checks)
  |> map(&all_checks_passed?/1)
  |> Effect.run(env)
end
```

### Use Plain Values When

```elixir
# ❌ Effect for pure computations
def calculate_tax_async(amount) do
  Effect.lift_func(fn -> amount * 0.1 end)
  |> Effect.run()
end

# ✅ Plain computation for pure functions
def calculate_tax(amount) do
  amount * 0.1
end
```

### Effect's Sweet Spot: Basic Async I/O

```elixir
# ✅ Effect is perfect for basic async operations
def fetch_user_dashboard(user_id, env) do
  parallel_requests = [
    fetch_user_profile(user_id),      # HTTP request
    fetch_recent_orders(user_id),     # Database query
    fetch_notifications(user_id)      # Cache lookup
  ]
  
  Effect.sequence_a(parallel_requests)
  |> map(&build_dashboard_response/1)
  |> Effect.run(env)
end

# ✅ Effect handles composition of discrete async operations well
def process_payment(payment_data, env) do
  Effect.right(payment_data)
  |> bind(&validate_payment_info/1)     # Quick validation
  |> bind(&charge_payment_processor/1)  # External API call
  |> bind(&update_user_account/1)       # Database update
  |> bind(&send_receipt_email/1)        # Email service call
  |> Effect.run(env)
end
```

## Architecture Decision Guide

**Use Effect for:**

- ✅ **Basic async I/O**: Database calls, HTTP requests, file operations
- ✅ **Composed workflows**: 3-10 step async pipelines with error handling
- ✅ **Request/response patterns**: Web request processing, API calls
- ✅ **Short-lived tasks**: Operations completing in seconds to minutes
- ✅ **Functional composition**: When you need monadic error handling

**Use OTP (GenServer/Agent) for:**

- ✅ **Stateful services**: Caches, connection pools, rate limiters  
- ✅ **Long-running processes**: Background workers, schedulers, monitors
- ✅ **System resources**: Database connections, file handles, network sockets
- ✅ **Fault tolerance**: Supervised processes that can restart
- ✅ **Message passing**: Actor-based communication patterns

**Use Job Queues (Oban/Exq) for:**

- ✅ **Reliable processing**: Jobs that must complete eventually
- ✅ **Back-pressure**: High-volume work that needs rate limiting
- ✅ **Persistence**: Jobs that survive application restarts
- ✅ **Retry logic**: Complex retry strategies with exponential backoff
- ✅ **Scheduled work**: Cron-like scheduling, delayed execution

**Use Streaming (Flow/Broadway) for:**  

- ✅ **Large datasets**: Processing data that doesn't fit in memory
- ✅ **Continuous streams**: Real-time event processing
- ✅ **ETL pipelines**: Extract, transform, load operations
- ✅ **Back-pressured workflows**: Producer/consumer with flow control
- ✅ **Parallel processing**: CPU-intensive batch operations

**Rule of thumb:** If it runs for more than a few minutes, processes thousands of items, or needs to survive application restarts, Effect is probably the wrong tool.

## Summary

Effect provides pure descriptions of asynchronous computations with full observability:

**Core Philosophy:**

- **Pure descriptions**: Effects are instructions, not computations - execution is deferred
- **Concurrent by default**: Effects run in parallel unless explicitly sequenced  
- **Controlled execution**: `run/2` provides isolation, timeouts, and telemetry
- **Evolutionary design**: Start simple, evolve complexity without changing interfaces

**Core Operations:**

- `right/2`: Describe successful async computations
- `left/2`: Describe failed async computations  
- `run/2`: Execute the deferred Effect with environment and full observability
- `map/2`: Transform success values while preserving async structure
- `bind/2`: Chain dependent operations sequentially with trace propagation
- `traverse_a/2`: Process lists in parallel with error accumulation

**Key Performance Patterns:**

- **Sequential processing**: Use `bind/2` for dependent operations (slower but controlled)
- **Parallel processing**: Use `traverse_a/2` for independent operations (faster)
- **Performance optimization**: `bind` cheap local checks before expensive I/O
- **Dependency injection**: Use Reader pattern for evolutionary architecture

**Mathematical Properties:**

- **Functor**: `map` preserves structure and async context  
- **Applicative**: `traverse_a` enables parallel processing with error collection
- **Monad**: `bind` enables dependent sequencing with automatic flattening
- **Reader**: `asks/1` enables dependency injection and evolutionary design
- **Monad Transformer**: Effect ≈ ReaderT (EitherT (Task a)) - Reader over Either over Task
- **Transformer note**: In languages with higher-kinded types, this could use transformer layering. In Elixir, Effect encodes the combined stack explicitly.

## Law Reference for LLM Verification

LLMs can use these mathematical laws to verify correctness of Effect code:

| Law | Operation | Expression | Validity |
|-----|-----------|------------|----------|
| **Functor Identity** | `map/2` | `map(fn x -> x end, fx) == fx` | ✅ Always |
| **Functor Composition** | `map/2` | `map(g, map(f, fx)) == map(fn x -> g.(f.(x)) end, fx)` | ✅ Always |
| **Applicative Identity** | `ap/2` | `ap(pure(fn x -> x end), fx) == fx` | ✅ Always |
| **Applicative Composition** | `ap/2` | Complex composition law with `pure` | ✅ Always |
| **Applicative Homomorphism** | `ap/2` | `ap(pure(f), pure(x)) == pure(f.(x))` | ✅ Always |
| **Monad Left Identity** | `bind/2` | `bind(pure(x), f) == f.(x)` | ✅ Always |
| **Monad Right Identity** | `bind/2` | `bind(mx, &pure/1) == mx` | ✅ Always |
| **Monad Associativity** | `bind/2` | `bind(bind(mx, f), g) == bind(mx, fn x -> bind(f.(x), g) end)` | ✅ Always |
| **Reader Ask Law** | `asks/1` | `asks(fn env -> env end) == ask()` | ✅ Always |
| **Reader Local Law** | Environment | Laws about environment propagation | ✅ Always |

**LLM Verification Usage**: If user code violates these laws, it's likely incorrect Effect usage.

**Observability Features:**

- Automatic telemetry emission with structured events
- Hierarchical span creation preserving trace lineage  
- Exception safety with structured `EffectError` wrapping
- Context propagation enabling distributed tracing

## Final LLM Guidance

**Effect in One Sentence**: Pure descriptions of observable async computations that enable both performance optimization and evolutionary design.

**Key Mental Model for LLMs**:

- **Construction**: Build pure descriptions (`Effect.right/left`)
- **Composition**: Chain with `bind` (sequential) or `traverse_a` (parallel)  
- **Execution**: Run with `run/2` for actual I/O with full observability
- **Context**: Thread environment for dependency injection via Reader pattern

**Critical Success Factors**:

1. **Always call `run/2`** - Effects are lazy descriptions until executed
2. **Use `bind` for dependent operations** - sequential execution with short-circuiting
3. **Use `traverse_a` for independent operations** - parallel execution with error accumulation
4. **Leverage context for evolutionary design** - inject dependencies, not configurations
5. **Trust the telemetry** - automatic observability with structured events

**When LLMs Should Recommend Effect**:

- User mentions async I/O, concurrency, observability, or tracing
- Building composed workflows with multiple async steps
- Need both fail-fast and error accumulation patterns
- Require dependency injection for evolutionary architecture

Remember: Effect represents "pure descriptions of observable async computation" - separate what to do from when to do it, enabling both performance optimization and evolutionary design.