lib/utils/usage-rules.md

# `Funx.Utils` Usage Rules

## LLM Functional Programming Foundation

**Key Concepts for LLMs:**

**Currying**: Converting a multi-argument function into a chain of single-argument functions

- `curry_r/1`: Curries arguments from right to left, allowing data argument to be applied last (pipeline-friendly)
- `curry/1` & `curry_l/1`: Left-to-right currying (traditional functional style)
- Example: `f(a, b, c)` → `curry(f).(a).(b).(c)`
- **Edge case**: Currying a unary function returns the original function (no-op)

**Partial Application**: Fixing some arguments of a function, creating a new function

- Result of currying: each step returns a function waiting for remaining args
- Enables configuration-first, data-last patterns
- Example: `add = curry_r(+).(5)` creates function that adds 5

**Point-Free Style**: Writing functions without explicitly mentioning arguments

- Compose functions without intermediate variables
- More declarative and reusable
- Example: `process = transform |> validate |> save`

**Function Flipping**: Reversing argument order for better composition

- `flip/1`: Swaps arguments of binary functions (arity = 2 only)
- Useful when argument order doesn't match pipeline needs
- Example: `flip(div).(2, 10)` → `10 / 2`
- **Invalid**: `flip/1` cannot be applied to unary or 3+ arity functions

**Arity Independence**: Works with functions of any number of arguments

- Dynamically inspects function arity via `:erlang.fun_info/2`
- Returns as many nested unary functions as the original function has parameters
- No need to know function arity in advance - curry supports arbitrary arity

## LLM Decision Guide: When to Use Utils

**✅ Use Utils when:**

- Building reusable, composable functions
- Need partial application for configuration
- Want point-free programming style
- Adapting functions for pipeline use
- User says: "configure then apply", "reuse with different parameters", "point-free"

**❌ Don't use Utils when:**

- Simple one-off function calls
- Performance is absolutely critical
- Argument order is already correct
- Functions are already curried

**⚡ Currying Strategy Decision:**

- **Pipeline-friendly**: Use `curry_r/1` (data flows left-to-right, config right-to-left)
- **Traditional FP**: Use `curry/1` or `curry_l/1` (left-to-right application)
- **Argument reordering**: Use `flip/1` then curry as needed

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

- **Configuration before data**: `curry_r(fn config, data -> ... end).(config)`
- **Traditional currying**: `curry(fn a, b, c -> ... end).(a).(b).(c)`
- **Argument order fix**: `flip(fn a, b -> ... end)`
- **Point-free composition**: Combine curried functions without variables

## LLM Context Clues

**User language → Utils patterns:**

- "configure then apply" → `curry_r` for config-first pattern
- "reuse with different settings" → curry for partial application
- "flip the arguments" → `flip/1`
- "point-free style" → curry functions for composition
- "pipeline-friendly" → `curry_r/1`
- "traditional currying" → `curry/1` or `curry_l/1`

## Quick Reference

- Use `curry_r/1` to curry functions right-to-left—ideal for Elixir's `|>` pipe style.
- Use `curry/1` or `curry_l/1` to curry left-to-right when needed.
- Use `flip/1` to reverse arguments in binary functions.
- All currying functions adapt to any arity and return nested unary functions.

## Overview

`Funx.Utils` provides functional utilities for reshaping multi-argument functions to support composition, partial application, and point-free style.
Use `curry_r/1` by default—it aligns with Elixir’s `|>` operator by shifting configuration to the right and leaving the data position first.

These tools are especially useful with predicates, monads, and other combinators where composition and reuse are key.

## Composition Rules

| Function    | Description                                                  |
| ----------- | ------------------------------------------------------------ |
| `curry_r/1` | Curries a function right-to-left (recommended for pipelines) |
| `curry/1`   | Curries left-to-right                                        |
| `curry_l/1` | Alias for `curry/1`, makes intent explicit                   |
| `flip/1`    | Reverses arguments of a binary function                      |

Each function returns a chain of unary functions that accumulate arguments until fully applied.

## Usage Patterns

### Right-to-Left Currying for Pipelines

```elixir
f = fn prefix, name -> prefix <> name end
greet = curry_r(f).("Hello, ")
"Alice" |> greet()
# => "Hello, Alice"
```

### Flip for Reordering Binary Functions

```elixir
div = fn a, b -> a / b end
flipped = flip(div)
flipped.(2, 10)  # => 5.0
```

### Left-to-Right Currying (Less Common)

```elixir
sum = curry(fn a, b, c -> a + b + c end)
sum.(1).(2).(3)  # => 6
```

## Guidelines for Composition

Follow these patterns when reshaping or generating function transformations:

- Prefer `curry_r/1` for functions that appear in pipelines.
- Use `flip/1` when argument order doesn’t match surrounding context.
- Use `curry/1` or `curry_l/1` when left-to-right application is more natural.
- Avoid manual `fn` nesting—use the provided helpers instead.
- Avoid assuming a function has only 2 or 3 arguments—curry_r/1 supports arbitrary arity and returns as many nested unary functions as needed.
- Avoid capture syntax (`&mod.fun/arity`) when partial application is needed.
  Capture locks arity and order, and cannot be curried or reordered.

Currying is often most useful when composed with lifted combinators like `map`, `bind`, `filter`, and so on.

## Stability Contract

Ensure that generated or transformed functions preserve:

- Purity — returned functions are referentially transparent.
- Laziness — arguments are applied only when provided.
- Equivalence — full application matches the original function:

```elixir
curry_r(f).(b).(a) == f.(a, b)
```

## Anti-Patterns

Avoid generating or emitting:

- Manual currying via nested `fn` chains: `fn a -> fn b -> f.(a, b) end end`
- Assuming functions have fixed arity (curry supports arbitrary arity dynamically)
- Argument reordering by hand—prefer `flip/1`
- Full application immediately after currying
- Capture syntax (`&fun/2`) when transformation is required

## Good Patterns

Encourage completions like:

```elixir
# Partial right-to-left application for pipelines
assign_role = curry_r(fn role, user -> %{user | role: role} end)
user |> assign_role.("admin")
```

```elixir
# Flipping to enable higher-order composition
compare = flip(fn a, b -> a > b end)
Enum.filter(list, compare.(10))
```

```elixir
# Contextual function with partial application
transform =
  curry_r(fn format, name -> format.("<" <> name <> ">") end)
"Alex" |> transform.(&String.upcase/1)
```

## When to Use

Reach for these utilities when you want to:

- Enable point-free style
- Compose partial functions within a pipeline
- Shift configuration before data
- Adapt argument order to match surrounding combinators
- Prepare functions before lifting into a monadic or applicative context

## Built-in Behavior

- `curry_r/1`, `curry/1`, and `curry_l/1` inspect function arity via `:erlang.fun_info/2`.
- Returned functions accumulate arguments until fully applied.
- `flip/1` applies only to functions of arity 2.

## LLM Code Templates

### Configuration-First Pattern Template

```elixir
# API client with configurable base settings
def build_api_client() do
  request_fn = curry_r(fn headers, auth, url ->
    HTTPoison.get(url, headers, auth: auth)
  end)
  
  # Pre-configure common settings
  authenticated_request = request_fn
    |> apply.([{"Content-Type", "application/json"}])
    |> apply.({:bearer, "token"})
  
  # Now just pass URLs
  "/users" |> authenticated_request.()
  "/posts" |> authenticated_request.()
end
```

### Data Transformation Pipeline Template

```elixir
def build_transformer() do
  # Curry transformation functions for reuse
  validate_with = curry_r(fn rules, data ->
    if Enum.all?(rules, fn rule -> rule.(data) end) do
      {:ok, data}
    else
      {:error, "validation failed"}
    end
  end)
  
  transform_with = curry_r(fn mapper, {:ok, data} -> {:ok, mapper.(data)} end)
  
  # Build reusable pipelines
  user_rules = [&is_adult/1, &has_email/1]
  user_validator = validate_with.(user_rules)
  user_transformer = transform_with.(&normalize_user/1)
  
  # Apply to data
  user_data 
  |> user_validator.()
  |> user_transformer.()
end
```

### Function Composition Template

```elixir
def build_processors() do
  # Flip functions to match pipeline argument order
  filter_by = flip(&Enum.filter/2)
  map_with = flip(&Enum.map/2)
  reduce_by = curry_r(&Enum.reduce/3)
  
  # Create specialized processors
  filter_adults = filter_by.(fn user -> user.age >= 18 end)
  extract_names = map_with.(fn user -> user.name end)
  count_items = reduce_by.(0, fn _, acc -> acc + 1 end)
  
  # Compose into pipeline
  users
  |> filter_adults.()
  |> extract_names.()
  |> count_items.()
end
```

### Predicate Factory Template

```elixir
def build_predicates() do
  # Create configurable predicates
  field_equals = curry_r(fn value, field, item ->
    Map.get(item, field) == value
  end)
  
  field_greater = curry_r(fn threshold, field, item ->
    Map.get(item, field) > threshold
  end)
  
  # Generate specific predicates
  is_admin = field_equals.(:admin, :role)
  is_adult = field_greater.(18, :age)
  is_active = field_equals.(true, :active)
  
  # Use with filtering
  users |> Enum.filter(is_admin)
  users |> Enum.filter(is_adult)
end
```

## LLM Performance Considerations

**Currying overhead:**

- Each curried function call has slight overhead
- Consider performance impact for hot paths
- Pre-curry functions used repeatedly

**Memory considerations:**

- Curried functions capture arguments in closures
- Can prevent garbage collection of captured values
- Use judiciously in long-running processes

**Thread-safety for closures:**

- Curried closures may capture config values
- In long-running processes, ensure captured values don't include large, stateful, or cyclic data
- Captured values should be immutable and reasonably sized

**Optimization patterns:**

```elixir
# ✅ Good: curry once, use many times
transformer = curry_r(&String.replace/3).("old", "new")
results = Enum.map(strings, transformer)

# ❌ Less efficient: curry in loop
results = Enum.map(strings, fn s -> 
  curry_r(&String.replace/3).("old", "new").(s)
end)
```

## LLM Interop Patterns

### With Enum Functions

```elixir
# Make Enum functions pipeline-friendly
map_with = flip(&Enum.map/2)
filter_by = flip(&Enum.filter/2)
reduce_by = curry_r(&Enum.reduce/3)

# Use in pipelines
data
|> filter_by.(predicate)
|> map_with.(transformer)
|> reduce_by.(initial_value, accumulator)
```

### With GenServer Calls

```elixir
# Create configured GenServer callers
def build_service_client(server_name) do
  call_server = curry_r(&GenServer.call/2).(server_name)
  cast_server = curry_r(&GenServer.cast/2).(server_name)
  
  %{
    get_user: call_server.({:get_user, user_id}),
    update_user: cast_server.({:update_user, user_data}),
    delete_user: cast_server.({:delete_user, user_id})
  }
end
```

### With Phoenix Contexts

```elixir
# Create context function factories
def build_user_operations(repo) do
  create_with_repo = curry_r(fn changeset, repo ->
    Repo.insert(changeset, repo: repo)
  end).(repo)
  
  update_with_repo = curry_r(fn changeset, user, repo ->
    Repo.update(changeset, repo: repo)
  end).(repo)
  
  %{
    create_user: create_with_repo,
    update_user: update_with_repo
  }
end
```

## LLM Testing Guidance

### Test Currying Behavior

```elixir
test "curry_r creates proper function chain" do
  add3 = fn a, b, c -> a + b + c end
  curried = Funx.Utils.curry_r(add3)
  
  # Test partial application
  partial1 = curried.(3)
  partial2 = partial1.(2)
  result = partial2.(1)
  
  assert result == 6
  assert add3.(1, 2, 3) == curried.(3).(2).(1)
end

test "curry_l creates left-to-right chain" do
  multiply3 = fn a, b, c -> a * b * c end
  curried = Funx.Utils.curry(multiply3)
  
  result = curried.(2).(3).(4)
  assert result == 24
end
```

### Test Function Flipping

```elixir
test "flip reverses binary function arguments" do
  subtract = fn a, b -> a - b end
  flipped = Funx.Utils.flip(subtract)
  
  assert subtract.(10, 3) == 7
  assert flipped.(3, 10) == 7
end
```

### Test Point-Free Composition

```elixir
test "curried functions compose for point-free style" do
  transform = Funx.Utils.curry_r(fn suffix, prefix, text ->
    prefix <> text <> suffix
  end)
  
  add_brackets = transform.("]", "[")
  add_parens = transform.(")", "(")
  
  assert add_brackets.("test") == "[test]"
  assert add_parens.("test") == "(test)"
end
```

## LLM Debugging Tips

### Test Individual Steps

```elixir
# Debug currying by testing each step
add3 = fn a, b, c -> a + b + c end
curried = curry_r(add3)

step1 = curried.(3)
IO.inspect(step1, label: "after first arg")

step2 = step1.(2) 
IO.inspect(step2, label: "after second arg")

result = step2.(1)
IO.inspect(result, label: "final result")
```

### Verify Equivalence

```elixir
# Ensure curried version equals original
original_result = original_fn.(arg1, arg2, arg3)
curried_result = curry_r(original_fn).(arg3).(arg2).(arg1)
assert original_result == curried_result
```

## LLM Error Message Design

### Handle Arity Mismatches

```elixir
def safe_curry(fun) do
  case :erlang.fun_info(fun, :arity) do
    {:arity, 0} -> {:error, "Cannot curry zero-arity function"}
    {:arity, n} when n > 0 -> {:ok, Funx.Utils.curry_r(fun)}
    _ -> {:error, "Invalid function"}
  end
end
```

### Provide Clear Function Descriptions

```elixir
def build_transformer(name, transform_fn) do
  curried = Funx.Utils.curry_r(transform_fn)
  
  # Add metadata for debugging
  fn config ->
    fn data ->
      try do
        curried.(config).(data)
      rescue
        error -> 
          {:error, "#{name} transformation failed: #{inspect(error)}"}
      end
    end
  end
end
```

## LLM Common Mistakes to Avoid

**❌ Don't use capture syntax with currying**

```elixir
# ❌ Wrong: capture syntax can't be curried
curry_r(&String.replace/3)

# ✅ Correct: use explicit function
curry_r(fn str, old, new -> String.replace(str, old, new) end)
```

**❌ Don't assume argument order**

```elixir
# ❌ Wrong: assuming curry_r argument order
divide = curry_r(fn a, b -> a / b end)
result = divide.(10).(2)  # This gives 0.2, not 5

# ✅ Correct: be explicit about order
divide = curry_r(fn divisor, dividend -> dividend / divisor end)
result = divide.(2).(10)  # This gives 5
```

**❌ Don't curry already curried functions**

```elixir
# ❌ Wrong: double currying
double_curried = curry_r(curry_r(fn a, b -> a + b end))

# ✅ Correct: curry once
curried = curry_r(fn a, b -> a + b end)
```

**❌ Don't ignore arity requirements**

```elixir
# ❌ Wrong: flip only works on binary functions
flip(fn a, b, c -> a + b + c end)  # Will error

# ✅ Correct: use flip only on binary functions
flip(fn a, b -> a + b end)
```

## Summary

`Funx.Utils` enables functional composition through currying and argument manipulation. Use it to build reusable, configurable functions that compose naturally in pipelines.

- **Right-to-left currying**: Use `curry_r/1` for Elixir pipeline style (data-last)
- **Left-to-right currying**: Use `curry/1` or `curry_l/1` for traditional functional style
- **Argument flipping**: Use `flip/1` to adapt binary functions for better composition
- **Point-free style**: Eliminate intermediate variables through function composition
- **Partial application**: Pre-configure functions with some arguments, apply data later
- **Arity independence**: Works with functions of any number of arguments dynamically