lib/foldable/usage-rules.md

# `Funx.Foldable` Usage Rules

## LLM Functional Programming Foundation

**Key Concepts for LLMs:**

**Foldable**: Protocol for collapsing any structure into a single value using conditional logic

- **`fold_l/3`**: Universal fold operation with present/absent function handling
- **`fold_r/3`**: Right-associative fold (same as fold_l for branching structures)
- **Polymorphic folding**: Same interface, different implementations based on structure type
- **Generic abstraction**: Folding is the universal pattern for "structure → single value"

**Core Pattern**: `fold(structure, present_func, absent_func)`

- **present_func**: Function called when structure contains value(s) 
- **absent_func**: Function called when structure is empty/absent
- **Result**: Single collapsed value from conditional logic

**Universal Folding Concept**: Every time you handle "what do I have, do different things based on that", you're conceptually folding. The protocol makes this pattern explicit and composable.

## LLM Decision Guide: When to Use Foldable

**✅ Use Foldable when:**

- Need to extract a concrete value from wrapped context (Maybe, Either)
- Collapsing collections into summary values
- Providing default values for empty/missing cases  
- Exit strategy from monadic pipelines back to concrete values
- **Prefer over imperative conditionals** (`case`, `cond`, `if/else`) for composability
- **Prefer over `Enum.reduce/3`** when working with potentially empty structures
- User says: "default value", "extract from", "handle both cases", "reconcile branches"

**⚡ Folding Strategy Decision:**

- **Context reconciliation** (Maybe/Either): Exit point from monadic pipeline
- **Collection aggregation** (List): Standard reduce/accumulation pattern  
- **Default provision**: Provide fallbacks for empty/missing cases
- **Branch handling**: Functional alternative to imperative case/cond logic

**⚙️ Function Choice Guide:**

- **Simple default values**: Use `Maybe.get_or_else/2` or `Either.get_or_else/2` instead of manual fold
- **Complex pipeline exit**: `fold_l(either_result, &success_handler/1, &error_handler/0)`
- **Collection summary**: `fold_l(list, &Enum.sum/1, fn -> 0 end)`
- **Direction choice**: Use `fold_l` as standard; `fold_r` only for ordered collections needing right-associative processing

## LLM Context Mapping

**User Intent → Foldable Patterns:**

- "get value or default" → `Maybe.get_or_else(maybe, default)` or `Either.get_or_else(either, default)`
- "handle success and error" → `fold_l(either, &process_success/1, &handle_error/0)`
- "extract from Maybe" → Use `Maybe.get_or_else/2` for simple cases, fold for complex transformations
- "sum all or zero" → `fold_l(list, &Enum.sum/1, fn -> 0 end)`
- "collapse pipeline result" → Use fold as final step to exit monadic context

## Overview

`Funx.Foldable` is a protocol that provides **polymorphic folding** - the universal pattern for collapsing any structure into a single value through conditional logic.

## Core Insight: Folding is Everywhere

Developers fold constantly without realizing it:

```elixir
# Pattern matching tagged tuples = folding
case fetch_user(id) do
  {:ok, user} -> user.name        # present_func
  {:error, _} -> "Unknown"        # absent_func  
end

# List reduction = folding
Enum.reduce([1, 2, 3], 0, &+/2)   # Standard fold/reduce

# Conditional logic = folding boolean structure
if user do
  process(user)                   # present_func
else
  handle_missing()                # absent_func
end
```

**Foldable makes this pattern explicit and composable** through protocol-based polymorphism.

## Functional Programming Preference

**In functional programming with Funx, prefer fold over:**

- **Imperative conditionals**: `case`, `cond`, `if/else` statements  
- **Elixir's `Enum.reduce/3`**: When working with structures that might be empty
- **Manual pattern matching**: Scattered conditional logic throughout code

**Why fold is better:**
- **Composable**: Works in pipelines and with other functional operations
- **Polymorphic**: Same interface across different data types
- **Consistent**: Unified approach to conditional logic
- **Safe**: Always handles both present and absent cases explicitly

## The Universal Folding Concept

**Generic Pattern**: `Structure + Logic → Single Value`

**Structure-Specific Implementations**:
- **Lists**: Traversal + accumulation (using Erlang's `:lists.foldl/3`)
- **Maybe/Either**: Conditional branching (present vs absent logic)
- **Predicates**: Evaluation + branching (true/false cases)
- **Tagged tuples**: Success/error reconciliation

**Same mental model everywhere**: "I have a structure, I want one value, here's logic for both cases."

## Core Operations

### `fold_l/3` - Universal Fold Operation

Collapses any structure using conditional functions:

```elixir
import Funx.Foldable

# Maybe: Extract with default
fold_l(Maybe.just(42), fn x -> x * 2 end, fn -> 0 end)  # 84
fold_l(Maybe.nothing(), fn x -> x * 2 end, fn -> 0 end)  # 0

# Either: Success/error handling  
fold_l(Either.right("data"), &String.upcase/1, fn -> "DEFAULT" end)  # "DATA"
fold_l(Either.left("error"), &String.upcase/1, fn -> "DEFAULT" end)   # "DEFAULT"

# List: Collection aggregation
fold_l([1, 2, 3], &Enum.sum/1, fn -> 0 end)  # 6
fold_l([], &Enum.sum/1, fn -> 0 end)         # 0
```

**Use `fold_l` for:**

- Extracting concrete values from wrapped contexts
- Providing defaults for empty cases
- Pipeline exit points (monadic context → concrete value)
- Standard choice for all folding operations

### `fold_r/3` - Right-Associative Fold

Right-associative folding for ordered collections:

```elixir
import Funx.Foldable

# For branching structures, direction is irrelevant
fold_r(Maybe.just(42), fn x -> x * 2 end, fn -> 0 end)  # 84 (same as fold_l)

# For ordered collections, direction affects traversal
fold_r([1, 2, 3], &build_right/2, fn -> initial end)  # Right-to-left processing
```

**Use `fold_r` when:**

- Working with ordered collections requiring right-associative folding
- Specific algorithmic needs for traversal direction
- **Note**: Identical to `fold_l` for branching structures (Maybe, Either, predicates)

## Folding Types: Two Categories

### 1. Branching Structures (Context Reconciliation)

**Purpose**: Exit strategy from monadic contexts

```elixir
# Maybe folding - handle presence/absence
def get_user_display_name(maybe_user) do
  fold_l(maybe_user, fn user -> user.name end, fn -> "Anonymous" end)
end

# Either folding - success/error reconciliation  
def process_api_result(either_result) do
  fold_l(
    either_result,
    fn success_data -> format_success(success_data) end,
    fn -> "Operation failed" end
  )
end

# Predicate folding - conditional execution
def branch_on_condition(predicate_fn) do
  fold_l(
    predicate_fn,
    fn -> "Condition met" end,      # If predicate returns true
    fn -> "Condition not met" end   # If predicate returns false
  )
end
```

**Characteristics**:
- **Direction irrelevant** - no traversal, just conditional logic
- **Present/absent semantics** 
- **Type reconciliation** from wrapped to concrete values
- **Pipeline exit points**

### 2. Ordered Collections (Aggregation/Reduction)

**Purpose**: Standard reduce operations using Erlang's fold functions

```elixir
# List aggregation
def safe_sum(list) do
  fold_l(list, fn items -> Enum.sum(items) end, fn -> 0 end)
end

# Collection statistics
def calculate_average(numbers) do
  fold_l(
    numbers,
    fn nums -> Enum.sum(nums) / length(nums) end,
    fn -> 0.0 end
  )
end

# First element with default
def first_or_default(list, default) do
  fold_l(list, fn [head | _] -> head end, fn -> default end)
end
```

**Characteristics**:
- **Direction matters** - left-to-right vs right-to-left processing
- **Accumulator patterns** - building up results
- **Uses Erlang's `:lists.foldl/3` and `:lists.foldr/3`**
- **Performance considerations** for large collections

## Common Folding Patterns

### 1. Pipeline Exit Pattern

**Problem**: Need concrete value from monadic pipeline
**Solution**: Use fold as final step

```elixir
# Manage control logic within pipeline, extract at end
result = 
  user_id
  |> fetch_user()                    # Maybe User - handles not found
  |> bind(&validate_permissions/1)   # Maybe User - handles validation
  |> map(&get_dashboard_data/1)      # Maybe Dashboard - transforms if valid
  |> filter(&has_recent_activity?/1) # Maybe Dashboard - conditional retention
  # All control logic managed within Maybe context ↑
  
  # Extract concrete value at pipeline boundary ↓
  |> fold_l(
      fn dashboard -> render_dashboard(dashboard) end,  # success path
      fn -> render_login_prompt() end                   # any failure path
    )
```

### 2. Default Value Pattern

**Problem**: Need fallbacks for missing/empty values
**Solution**: Use convenience functions for simple cases, fold for complex cases

```elixir
# ✅ Simple default - use convenience functions
def with_simple_default(maybe_value, default) do
  Maybe.get_or_else(maybe_value, default)
end

def with_simple_either_default(either_value, default) do
  Either.get_or_else(either_value, default)
end

# ✅ Complex transformations - use fold
def with_complex_transformation(maybe_value) do
  fold_l(
    maybe_value, 
    fn value -> transform_and_process(value) end,
    fn -> expensive_computation() end
  )
end

# Chained fallbacks
def first_available(maybes) when is_list(maybes) do
  Enum.reduce(maybes, Maybe.nothing(), fn maybe, acc ->
    fold_l(acc, &Maybe.just/1, fn -> maybe end)
  end)
end
```

### 3. Tagged Tuple Reconciliation Pattern

**Problem**: Handle `{:ok, value}` / `{:error, reason}` results
**Solution**: Convert to Either, then fold

```elixir
def handle_api_call(params) do
  params
  |> make_api_request()              # {:ok, data} | {:error, reason}
  |> Either.from_tagged()            # Either.Right | Either.Left
  |> fold_l(
      fn data -> process_success(data) end,
      fn -> handle_api_error() end
    )
end
```

### 4. Collection Aggregation Pattern

**Problem**: Safely aggregate collections that might be empty
**Solution**: Use fold with aggregation and default functions

```elixir
# Safe mathematical operations
def safe_average(numbers) when is_list(numbers) do
  case numbers do
    [] -> fold_l(Maybe.nothing(), &Function.identity/1, fn -> 0.0 end)
    nums -> fold_l(Maybe.just(nums), fn ns -> Enum.sum(ns) / length(ns) end, fn -> 0.0 end)
  end
end

# Resource utilization
def calculate_usage(maybe_metrics) do
  fold_l(
    maybe_metrics,
    fn metrics ->
      %{
        total: Enum.sum(metrics),
        average: Enum.sum(metrics) / length(metrics),
        peak: Enum.max(metrics)
      }
    end,
    fn -> %{total: 0, average: 0.0, peak: 0} end
  )
end
```

## Protocol Implementations

Foldable uses **protocol-based polymorphism** - same interface, different runtime behavior:

### Branching Structures (Conditional Logic)

**Maybe Types**:
- `Just`: Calls `present_func` with wrapped value
- `Nothing`: Calls `absent_func` with no arguments

**Either Types**:
- `Right`: Calls `present_func` with success value  
- `Left`: Calls `absent_func` (ignores error details)

**Predicates (Functions)**:
- Evaluates predicate function
- Calls `present_func` if true, `absent_func` if false

### Ordered Collections (Traversal Logic)

**Lists**:
- Non-empty: Calls `present_func` with entire list
- Empty: Calls `absent_func`
- Uses Erlang's `:lists.foldl/3` and `:lists.foldr/3` internally

**Ranges**:
- Non-empty: Calls `present_func` with range
- Empty: Calls `absent_func`  
- Direction affects traversal order

## Integration with Other Protocols

### Fold + Monad (Pipeline Exit)

```elixir
import Funx.Foldable  
import Funx.Monad

# Monadic pipeline with fold extraction
def user_dashboard_workflow(user_id) do
  user_id
  |> fetch_user()                    # Maybe User
  |> bind(&validate_user/1)          # Maybe ValidUser  
  |> map(&build_dashboard/1)         # Maybe Dashboard
  |> fold_l(
      fn dashboard -> {:ok, dashboard} end,
      fn -> {:error, :user_not_found} end
    )
end
```

### Fold + Filter (Conditional Aggregation)

```elixir
import Funx.Foldable
import Funx.Filterable

# Filter then aggregate
def process_valid_data(maybe_users) do
  maybe_users
  |> filter(fn users -> length(users) > 0 end)    # Keep non-empty
  |> fold_l(fn users -> analyze_users(users) end, fn -> default_analysis() end)
end
```

## Performance Considerations

### Lazy Evaluation Benefits

```elixir
# Expensive computations only execute when needed
fold_l(
  maybe_data,
  fn data -> expensive_processing(data) end,    # Only runs if present
  fn -> expensive_default() end                 # Only runs if absent
)
```

### Short-Circuiting for Empty Structures

```elixir
# Early return for empty cases
fold_l(
  empty_list,
  fn _ -> complex_calculation() end,    # Never executes
  fn -> 0 end                          # Returns immediately
)
```

### Memory Efficiency

- **No intermediate collections** created during folding
- **Direct value transformation** without temporary structures
- **Tail recursion optimization** for large list folds

## Troubleshooting Common Issues

### Issue: Missing Absent Function

```elixir
# ❌ Problem: Only considering present case
fold_l(maybe_user, fn user -> user.name end)  # Compiler error!

# ✅ Solution: Always provide both functions  
fold_l(maybe_user, fn user -> user.name end, fn -> "Unknown" end)
```

### Issue: Wrong Function Arity

```elixir
# ❌ Problem: absent_func expecting parameters
fold_l(maybe_value, fn x -> x * 2 end, fn default -> default end)  # Wrong!

# ✅ Solution: absent_func takes no arguments
fold_l(maybe_value, fn x -> x * 2 end, fn -> default_value end)
```

### Issue: Confusing Fold Direction

```elixir
# ❌ Problem: Thinking direction matters for Maybe/Either
fold_r(maybe_value, present_func, absent_func)  # Same as fold_l!

# ✅ Understanding: Direction only matters for ordered collections
fold_l(maybe_value, present_func, absent_func)  # Standard choice
fold_r([1,2,3], combine_func, default_func)     # Direction affects traversal
```

### Issue: Using Fold Instead of Map/Bind

```elixir
# ❌ Problem: Using fold when you want to stay in context
fold_l(maybe_user, fn user -> Maybe.just(user.name) end, fn -> Maybe.nothing() end)

# ✅ Solution: Use map to transform within context
map(maybe_user, fn user -> user.name end)  # Stays in Maybe context
```

## When NOT to Use Foldable

### Use Convenience Functions for Simple Default Values

```elixir
# ❌ Manual fold for simple default values
fold_l(maybe_user, &Function.identity/1, fn -> "Anonymous" end)
fold_l(either_result, &Function.identity/1, fn -> "Error" end)

# ✅ Use convenience functions
Maybe.get_or_else(maybe_user, "Anonymous")
Either.get_or_else(either_result, "Error")
```

### Use Map When Staying in Context

```elixir
# ❌ Fold to transform while keeping structure
fold_l(maybe_user, fn user -> Maybe.just(transform(user)) end, fn -> Maybe.nothing() end)

# ✅ Map to transform while preserving context
map(maybe_user, &transform/1)  # Result is still Maybe
```

### Use Bind for Monadic Chaining

```elixir
# ❌ Fold for operations returning wrapped values
fold_l(maybe_user, fn user -> fetch_profile(user) end, fn -> Maybe.nothing() end)

# ✅ Bind for monadic sequencing
bind(maybe_user, &fetch_profile/1)  # Flattens nested Maybe
```

### Use Filter for Conditional Retention

```elixir
# ❌ Fold for conditional value retention
fold_l(maybe_value, fn x -> if x > 0, do: Maybe.just(x), else: Maybe.nothing() end, ...)

# ✅ Filter for conditional retention within context
filter(maybe_value, fn x -> x > 0 end)
```

## Best Practices

### 1. Fold as Universal Recursion Eliminator

Prefer fold over explicit pattern matching for composability:

```elixir
# ❌ Imperative: Scattered case statements
case result do
  %Either.Right{right: value} -> process(value)
  %Either.Left{} -> default
end

# ✅ Functional: Consistent fold interface  
fold_l(result, &process/1, fn -> default end)

# ❌ Imperative: Manual reduce with conditionals
case numbers do
  [] -> 0
  nums -> Enum.reduce(nums, 0, &+/2)
end

# ✅ Functional: Fold handles empty case automatically
fold_l(numbers, &Enum.sum/1, fn -> 0 end)
```

### 2. Stay in Context, Fold at Boundaries

Keep computations in monadic context as long as possible:

```elixir
# Do all transformations in wrapped context
result = 
  input
  |> Maybe.pure()
  |> map(&transform1/1)
  |> bind(&transform2/1)
  |> map(&transform3/1)
  # Stay in Maybe context ↑
  |> fold_l(&finalize/1, fn -> default_result end)  # Exit to concrete value ↓
```

### 3. Fold + Monoid for Powerful Aggregation

Combine folding with monoid operations:

```elixir
# Aggregate with monoid combination
scores
|> Enum.map(&calculate_score/1)           # Transform to scores
|> fold_l(&Enum.sum/1, fn -> 0 end)       # Aggregate with + monoid
```

### 4. Type-Driven Folding

Let types guide fold usage:
- **Have wrapped value, need concrete result** → Use fold
- **Have collection, need summary** → Use fold  
- **Have computation that might fail, need final result** → Use fold

## Summary

Foldable provides **polymorphic folding** - the universal pattern for collapsing structures into single values:

**Core Operations:**

- `fold_l/3`: Universal fold operation (standard choice)
- `fold_r/3`: Right-associative fold (for ordered collections needing specific direction)

**Two Folding Categories:**

- **Branching structures** (Maybe, Either, predicates): Context reconciliation through conditional logic
- **Ordered collections** (List, Range): Aggregation/reduction using Erlang's fold functions

**Key Patterns:**

- **Pipeline exit**: Extract concrete values from monadic contexts
- **Default provision**: Handle empty/missing cases with fallbacks
- **Tagged tuple reconciliation**: Convert success/error tuples to single results  
- **Collection aggregation**: Safely collapse collections with default handling

**Universal Insight:**

Folding is everywhere in programming - pattern matching, conditionals, reductions are all forms of folding. The `Foldable` protocol makes this pattern **explicit, composable, and polymorphic**.

**Functional Programming Philosophy:**

In Funx, **prefer fold over imperative conditionals and manual reduce operations**. Fold provides:
- **Unified interface** across all data types
- **Composable operations** that work in pipelines  
- **Explicit handling** of both success and failure cases
- **Type safety** through protocol dispatch

**Mental Model**: "I have a structure that might be empty, I need a concrete value, here's logic for both cases."

Remember: **Manage context-specific control logic within the pipeline, then fold to extract at the end.**