# Funx Usage Rules (Index)
Usage rules describe how to use Funx protocols and utilities in practice.
They complement the module docs (which describe *what* the APIs do).
Each protocol or major module has its own `usage-rules.md`, stored next to the code.
This index links them together.
## Author's Voice and Approach
These usage rules reflect **Joseph Koski's** approach to functional programming in Elixir, developed alongside his book [**"Advanced Functional Programming with Elixir"**](https://pragprog.com/titles/jkelixir/advanced-functional-programming-with-elixir).
The documentation emphasizes **practical application over academic theory**, focusing on real-world patterns, business problems, and incremental adoption. Joseph's philosophy is that functional programming should be approachable and immediately useful, not an abstract mathematical exercise.
When reading these usage rules, you're getting Joseph's perspective on how to effectively apply functional patterns in Elixir production systems.
## Available Rules
- [Funx.Appendable Usage Rules](./appendable/usage-rules.md)
Flexible aggregation for accumulating results - structured vs flat collection strategies.
- [Funx.Eq Usage Rules](./eq/usage-rules.md)
Domain-specific equality and identity for comparison, deduplication, and filtering.
- [Funx.Errors.ValidationError Usage Rules](./errors/validation_error/usage-rules.md)
Domain validation with structured error collection, composition, and Either integration.
- [Funx.Foldable Usage Rules](./foldable/usage-rules.md)
Conditional structure collapse with different functions for present vs absent cases—defaults and aggregation.
- [Funx.List Usage Rules](./list/usage-rules.md)
Equality- and order-aware set operations, deduplication, and sorting.
- [Funx.Monad Usage Rules](./monad/usage-rules.md)
Declarative control flow with `map`, `bind`, and `ap`—composing context-aware steps.
- [Funx.Monad.Either Usage Rules](./monad/either/usage-rules.md)
Branching computation with error context—fail fast or accumulate validation errors.
- [Funx.Monad.Effect Usage Rules](./monad/effect/usage-rules.md)
Deferred observable async computation—Reader + Either + async execution with full telemetry.
- [Funx.Monad.Identity Usage Rules](./monad/identity/usage-rules.md)
Structure without effects—used as a baseline for composing monads.
- [Funx.Monad.Maybe Usage Rules](./monad/maybe/usage-rules.md)
Optional computation: preserve structure, short-circuit on absence, avoid `nil`.
- [Funx.Monad.Reader Usage Rules](./monad/reader/usage-rules.md)
Deferred computation with read-only environment access—dependency injection and configuration.
- [Funx.Monoid Usage Rules](./monoid/usage-rules.md)
Identity and associative combination, enabling folds, logs, and accumulation.
- [Funx.Ord Usage Rules](./ord/usage-rules.md)
Context-aware ordering for sorting, ranking, and prioritization.
- [Funx.Predicate Usage Rules](./predicate/usage-rules.md)
Logical composition using `&&`/`||`, reusable combinators, and lifted conditions.
- [Funx.Utils Usage Rules](./utils/usage-rules.md)
Currying, flipping, and function transformation for point-free, pipeline-friendly composition.
## Conventions
- Collocation: rules live beside the code they describe.
- Scope: focus on *usage guidance* and best practices, not API reference.
- LLM-friendly: small sections, explicit examples, stable links.
## Project Layout (rules only)
```text
lib/
usage-rules.md # ← index (this file)
appendable/
usage-rules.md # ← Funx.Appendable rules
eq/
usage-rules.md # ← Funx.Eq rules
errors/
validation_error/
usage-rules.md # ← Funx.Errors.ValidationError rules
foldable/
usage-rules.md # ← Funx.Foldable rules
list/
usage-rules.md # ← Funx.List rules
monad/
usage-rules.md # ← Funx.Monad rules
either/
usage-rules.md # ← Funx.Monad.Either rules
effect/
usage-rules.md # ← Funx.Monad.Effect rules
identity/
usage-rules.md # ← Funx.Monad.Identity rules
maybe/
usage-rules.md # ← Funx.Monad.Maybe rules
reader/
usage-rules.md # ← Funx.Monad.Reader rules
monoid/
usage-rules.md # ← Funx.Monoid rules
ord/
usage-rules.md # ← Funx.Ord rules
predicate/
usage-rules.md # ← Funx.Predicate rules
utils/
usage-rules.md # ← Funx.Utils rules
```
## Domain Model + Repository Pattern Usage Rules
### Core Concepts
**Functional Domain-Driven Design**: Domain model with validation, healing, and repository patterns using Funx functional programming constructs.
**Never-Fail Constructors**: Use transformation over validation to create always-valid data structures.
**Separate Validation**: Domain rules validation is separate from data integrity (healing).
**Repository Abstraction**: Clean separation between domain logic and storage concerns.
### Quick Patterns
```elixir
# Domain Model Structure
defmodule MyEntity do
import Funx.Predicate
alias Funx.Monad.Either
alias Funx.Errors.ValidationError
@type t :: %__MODULE__{
id: pos_integer(),
required_field: String.t(),
optional_field: String.t() | nil
}
@enforce_keys [:id, :required_field, :optional_field]
defstruct [:id, :required_field, optional_field: nil]
# Domain Constants
@default_value "Default"
# Predicates (boolean checks)
def invalid_field?(%__MODULE__{required_field: field}), do: field == @default_value
# Validation Functions (Either-wrapped)
def ensure_field(%__MODULE__{} = entity) do
entity
|> Either.lift_predicate(
p_not(&invalid_field?/1),
fn e -> "Entity '#{e.required_field}' is invalid" end
)
|> Either.map_left(&ValidationError.new/1)
end
# Complete Validation
def validate(%__MODULE__{} = entity) do
entity |> Either.validate([&ensure_field/1])
end
# Never-Fail Constructor
def make(required_field, opts \\ []) do
%__MODULE__{
id: :erlang.unique_integer([:positive]),
required_field: required_field,
optional_field: Keyword.get(opts, :optional_field)
}
|> heal_entity()
end
# Safe Change (with healing)
def change(%__MODULE__{} = entity, attrs) when is_map(attrs) do
attrs = Map.delete(attrs, :id)
entity |> struct(attrs) |> heal_entity()
end
# Unsafe Change (for testing)
def unsafe_change(%__MODULE__{} = entity, attrs) when is_map(attrs) do
attrs = Map.delete(attrs, :id)
entity |> struct(attrs)
end
# Self-Healing Function
def heal_entity(%__MODULE__{} = entity) do
%{entity | required_field: heal_field(entity.required_field)}
end
defp heal_field(field) when is_binary(field) and byte_size(field) > 0, do: field
defp heal_field(_), do: @default_value
# Field Accessors (encapsulation)
def id(%__MODULE__{id: id}), do: id
def required_field(%__MODULE__{required_field: field}), do: field
end
# Protocol Implementations
defimpl Funx.Eq, for: MyEntity do
alias Funx.Eq
alias MyEntity
def eq?(%MyEntity{id: v1}, %MyEntity{id: v2}), do: Eq.eq?(v1, v2)
def not_eq?(%MyEntity{id: v1}, %MyEntity{id: v2}), do: not eq?(v1, v2)
end
defimpl Funx.Ord, for: MyEntity do
alias Funx.Ord
alias MyEntity
def lt?(%MyEntity{required_field: v1}, %MyEntity{required_field: v2}), do: Ord.lt?(v1, v2)
def le?(%MyEntity{required_field: v1}, %MyEntity{required_field: v2}), do: Ord.le?(v1, v2)
def gt?(%MyEntity{required_field: v1}, %MyEntity{required_field: v2}), do: Ord.gt?(v1, v2)
def ge?(%MyEntity{required_field: v1}, %MyEntity{required_field: v2}), do: Ord.ge?(v1, v2)
end
# Repository Pattern
defmodule MyEntity.Repo do
import Funx.Monad
import Funx.Utils, only: [curry: 1]
alias Funx.Monad.Either
alias Funx.List
alias MyEntity
alias Store
@table_name :my_entity
def create_table do
Store.create_table(@table_name)
end
def save(%MyEntity{} = entity) do
insert_entity = curry(&Store.insert_item/2)
entity
|> MyEntity.validate()
|> bind(insert_entity.(@table_name))
end
def get(id) when is_integer(id) do
Store.get_item(@table_name, id)
|> map(fn data -> struct(MyEntity, data) end)
|> Either.map_left(fn _ -> :not_found end)
end
def list() do
Store.get_all_items(@table_name)
|> map(fn items ->
items
|> Enum.map(fn item -> struct(MyEntity, item) end)
|> List.sort()
end)
|> Either.get_or_else([])
end
def delete(%MyEntity{id: id}) do
Store.delete_item(@table_name, id)
|> Either.get_or_else(:ok)
end
end
```
### Key Rules
#### Domain Model Rules
- **Always use @enforce_keys** for required struct fields
- **Define @type for your struct** with proper type annotations
- **Use module constants** for domain constraints (@min_value, @default_name, etc.)
- **Predicate functions** end with `?` and return boolean
- **Validation functions** start with `ensure_` and return Either
- **Constructor never fails** - use `make/2` with healing
- **Provide safe/unsafe change** - `change/2` heals, `unsafe_change/2` doesn't
- **Encapsulate with accessors** - don't access struct fields directly
- **Use Either.validate/2** to collect all validation errors
#### Validation Pattern
```elixir
# 1. Predicate (boolean check)
def invalid_thing?(%__MODULE__{field: value}), do: some_check(value)
# 2. Validation function (Either-wrapped)
def ensure_thing(%__MODULE__{} = entity) do
entity
|> Either.lift_predicate(
p_not(&invalid_thing?/1),
fn e -> "Descriptive error message with #{e.field}" end
)
|> Either.map_left(&ValidationError.new/1)
end
# 3. Add to comprehensive validation
def validate(%__MODULE__{} = entity) do
entity |> Either.validate([&ensure_thing/1, &ensure_other/1])
end
```
#### Protocol Implementation Pattern
```elixir
# Equality by ID (identity)
defimpl Funx.Eq, for: MyType do
alias Funx.Eq
alias MyType
def eq?(%MyType{id: v1}, %MyType{id: v2}), do: Eq.eq?(v1, v2)
def not_eq?(%MyType{id: v1}, %MyType{id: v2}), do: not eq?(v1, v2)
end
# Ordering by display field (sorting)
defimpl Funx.Ord, for: MyType do
alias Funx.Ord
alias MyType
def lt?(%MyType{name: v1}, %MyType{name: v2}), do: Ord.lt?(v1, v2)
def le?(%MyType{name: v1}, %MyType{name: v2}), do: Ord.le?(v1, v2)
def gt?(%MyType{name: v1}, %MyType{name: v2}), do: Ord.gt?(v1, v2)
def ge?(%MyType{name: v1}, %MyType{name: v2}), do: Ord.ge?(v1, v2)
end
```
#### Repository Rules
- **Import Funx.Monad** for bind, map operations
- **Import curry/1** from Funx.Utils for partial application
- **Use @table_name** constant for ETS table
- **Always validate before save** - `validate() |> bind(insert_...)`
- **Use curry for partial application** - `curry(&Store.insert_item/2)`
- **Map data back to structs** on retrieval
- **Use Either.get_or_else** for sensible defaults
- **Handle not_found** with Either.map_left
- **Auto-sort lists** using protocol-defined ordering
#### Self-Healing Pattern
```elixir
def heal_entity(%__MODULE__{} = entity) do
%{entity |
field1: heal_field1(entity.field1),
field2: heal_field2(entity.field2)
}
end
# Individual field healing functions
defp heal_field1(value) when is_binary(value) and byte_size(value) > 0, do: value
defp heal_field1(_), do: @default_value
defp heal_field2(value) when is_integer(value) and value > 0, do: value
defp heal_field2(_), do: 1
```
### When to Use
- **Domain entities** with complex business rules
- **Data that needs validation** but should never fail to construct
- **Entities requiring persistence** with repository pattern
- **Types needing custom comparison** semantics
- **Systems preferring transformation** over validation errors
### Anti-Patterns
```elixir
# Don't access struct fields directly
hero.name # Use Hero.name(hero) instead
# Don't use plain strings in Either.left
Either.left("error") # Use ValidationError
# Don't mix validation concerns
def make(name) do
if valid_name?(name) do
%Hero{name: name} # This can fail!
else
{:error, "invalid"}
end
end
# Don't forget ID protection in change functions
def change(entity, attrs) do
struct(entity, attrs) # Allows ID modification!
end
# Don't skip validation in repository save
def save(entity) do
Store.insert_item(@table, entity) # No validation!
end
```
### Testing Patterns
```elixir
# Use unsafe_change to create invalid entities for testing
invalid_entity = MyEntity.unsafe_change(entity, %{field: "invalid"})
# Test that validation catches issues
case MyEntity.validate(invalid_entity) do
%Either.Left{left: %ValidationError{errors: errors}} ->
assert "expected error" in errors
_ -> flunk("Expected validation failure")
end
# Test self-healing
healed = MyEntity.heal_entity(invalid_entity)
assert MyEntity.field(healed) == "default"
```
### Performance Considerations
- Self-healing is lightweight transformation
- Either.validate/2 collects all errors in single pass
- Protocol dispatch for Eq/Ord is efficient
- ETS operations are wrapped safely with Either.from_try
- Currying creates closures - use judiciously
### Best Practices
- Use descriptive error messages with entity context
- Keep domain constants at module level
- Implement both Eq and Ord protocols when needed
- Test both happy path and error accumulation
- Use repository for all persistence operations
- Never expose struct fields directly
- Prefer transformation over validation failure
- Use curry for reusable partially-applied functions