# `Funx.Monoid` Usage Rules
## LLM Functional Programming Foundation
**Key Concepts for LLMs:**
**CRITICAL Elixir Implementation**: All monoid operations are under `Funx.Monoid` protocol
- **NO separate Semigroup protocol** - Elixir protocols cannot be extended after definition
- Always use `Monoid.empty/1`, `Monoid.append/2` or import `Funx.Monoid`
- Different from Haskell's separate Semigroup and Monoid typeclasses
**Monoid Protocol**: Mathematical structure for associative combination with identity
- `empty/1`: Returns the identity element for the monoid (like 0 for addition, [] for lists)
- `append/2`: Associative binary operation that combines two values
- `wrap/2` / `unwrap/1`: Infrastructure functions to convert between raw values and monoid wrappers
- Example: `Monoid.append(%Sum{}, 5, 3)` combines numbers using addition
**Monoid Laws**: Mathematical guarantees that ensure predictable behavior
- **Left Identity**: `append(empty(m), x) === x`
- **Right Identity**: `append(x, empty(m)) === x`
- **Associativity**: `append(append(a, b), c) === append(a, append(b, c))`
- These laws enable safe composition and parallel computation
- Example: Summing `[1,2,3,4]` can be computed as `(1+2)+(3+4)` or `1+(2+(3+4))`
**Algebraic Data Combination**: Monoids represent ways to combine data
- **Sum**: Numbers with addition (`0` identity, `+` operation)
- **Product**: Numbers with multiplication (`1` identity, `*` operation)
- **Max/Min**: Numbers with comparison (`-∞/+∞` identity, `max/min` operation)
- **List**: Collections with concatenation (`[]` identity, `++` operation)
- **All/Any**: Booleans with AND/OR (`true/false` identity, `&&/||` operation)
- Example: Combining user preferences uses monoid to merge settings
**Higher-Level Abstractions**: Monoids power utility functions
- `Math.sum/1`, `Math.product/1` use numeric monoids internally
- `Eq.Utils.concat_all/1` uses All monoid for AND-ing equality checks
- `Predicate.p_all/1` uses All monoid for combining boolean predicates
- Example: `Math.sum([1,2,3])` internally uses Sum monoid but hides the complexity
## LLM Decision Guide: When to Use Monoid Protocol
**✅ Use Monoid Protocol when:**
- Need associative combination with identity (merging, accumulating, folding)
- Building reusable combination logic for custom data types
- Want parallel/distributed computation guarantees
- Creating utility functions that combine multiple values
- User says: "combine", "merge", "accumulate", "fold", "reduce"
**❌ Don't use Monoid Protocol when:**
- Simple one-off combinations (use direct operations like `+`, `++`)
- Non-associative operations (like subtraction or division)
- No meaningful identity element exists
- Operations have side effects or are non-deterministic
**⚡ Monoid Strategy Decision:**
- **Built-in types**: Use existing monoids (Sum, Product, Max, Min, ListConcat)
- **Custom combination**: Define new monoid struct and protocol implementation
- **Application code**: Use high-level utilities (`Math`, `Eq.Utils`, `Ord.Utils`)
- **Library code**: Expose monoids through utility functions, not raw protocol
**⚙️ Function Choice Guide (Mathematical Purpose):**
- **Identity element**: `empty/1` to get neutral value for combination
- **Binary combination**: `append/2` to combine two values associatively
- **Multiple combination**: `m_concat/2` to combine a list of values
- **Utility helpers**: `m_append/3` for low-level monoid operations
## LLM Context Clues
**User language → Monoid patterns:**
- "combine all these values" → Use `m_concat/2` or utility functions
- "merge with defaults" → Use monoid with appropriate identity element
- "accumulate results" → Use monoid for associative accumulation
- "parallel computation" → Monoids enable safe parallelization
- "sum/product/max/min" → Use `Math` utilities backed by monoids
- "AND/OR logic" → Use `All/Any` monoids for boolean combination
## Quick Reference
- A monoid = `empty/1` (identity) + `append/2` (associative).
- Identities must be true identities (e.g. `0` for sum, `1` for product, `[]` for concatenation).
- `wrap/2` and `unwrap/1` exist for infrastructure, not daily use.
- `m_append/3` and `m_concat/2` are low-level helpers that power higher abstractions.
- Application code should prefer helpers in `Math`, `Eq.Utils`, `Ord.Utils`, or `Predicate`.
## Overview
`Funx.Monoid` defines how values combine under an associative operation with an identity.
Each monoid is represented by a struct (e.g. `%Sum{}`, `%Product{}`, `%Eq.All{}`, `%Ord{}`) and implements:
- `Monoid.empty/1` → the identity element
- `Monoid.append/2` → associative combination
- `wrap/2` / `unwrap/1` → convert between raw values and monoid structs
**Important Implementation Detail**: Unlike Haskell's separate Semigroup and Monoid typeclasses, Elixir's protocol system limitations require all operations under the single `Funx.Monoid` protocol.
Monoids are rarely used directly in application code. Instead, they support utilities like `Math.sum/1`, `Eq.Utils.concat_all/1`, and `Ord.Utils.concat/1`.
## Protocol Rules
- Provide all four functions: `empty/1`, `append/2`, `wrap/2`, `unwrap/1`.
- Identity: `append(empty(m), x) == x == append(x, empty(m))`.
- Associativity: `append(append(a, b), c) == append(a, append(b, c))`.
- Purity: results must be deterministic and side-effect free.
**Note**: While not typically needed, you can define a `join/1` operation for monoids that flattens nested monoid values (e.g., combining lists of lists) using `m_concat/2`. This provides symmetry with Monad operations for flattening nested structures.
## Preferred Usage
### Go Through Utilities
Use high-level helpers instead of wiring monoids manually:
- **Numbers** → `Math.sum/1`, `Math.product/1`, `Math.max/1`, `Math.min/1`
- **Equality** → `Eq.Utils.concat_all/1`, `Eq.Utils.concat_any/1`
- **Ordering** → `Ord.Utils.concat/1`, `Ord.Utils.append/2`
- **Predicates** → `Predicate.p_and/2`, `Predicate.p_or/2`, `Predicate.p_all/1`, `Predicate.p_any/1`
These functions already call `m_concat/2` and `m_append/3`.
You don't need to construct `%Monoid.*{}` by hand.
### Examples
#### Equality Composition
```elixir
alias Funx.Eq.Utils, as: EqU
name_eq = EqU.contramap(& &1.name)
age_eq = EqU.contramap(& &1.age)
EqU.concat_all([name_eq, age_eq]) # AND semantics
EqU.concat_any([name_eq, age_eq]) # OR semantics
```
#### Ordering Composition
```elixir
alias Funx.Ord.Utils, as: OrdU
age = OrdU.contramap(& &1.age)
name = OrdU.contramap(& &1.name)
OrdU.concat([age, name]) # lexicographic ordering
```
#### Math Helpers
```elixir
alias Funx.Math
Math.sum([1, 2, 3]) # => 6
Math.product([2, 3, 4]) # => 24
Math.max([7, 3, 5]) # => 7
Math.min([7, 3, 5]) # => 3
```
## Interop
- `Eq.Utils` relies on `Eq.All` and `Eq.Any` monoids for composition.
- `Ord.Utils` uses the `Ord` monoid for lexicographic comparison.
- `Math` uses monoids for numeric folds.
**Rule of thumb:** application code never wires `%Monoid.*{}` directly—always go through the utility combinators.
## Stability Contract
- Identities must be stable and input-independent.
- `append/2` must be associative for all valid values.
- `wrap/2` and `unwrap/1` must be inverses.
## Anti-Patterns
- Hand-wiring `%Monoid.*{}` in application code.
- Mixing different monoid types in one `append/2`.
- Using fake identities (`nil` instead of `0` for sum).
- Hiding side effects inside protocol functions.
**Type Safety Warning**: Always ensure values passed to `append/2` are of the same wrapped monoid type:
```elixir
# ❌ Wrong - mixing types
append(%Sum{}, 1, %Product{value: 2}) # Invalid - type mismatch
# ✅ Right - consistent types
append(%Sum{}, 1, 2) # OK: both values are integers
```
## Good Patterns
- Use `Math`, `Eq.Utils`, `Ord.Utils`, or `Predicate` instead of raw monoids.
- Keep identities explicit in library code (`0`, `1`, `[]`, `Float.min_finite()` / `Float.max_finite()`).
- Let `m_concat/2` and `m_append/3` handle the wrapping/combining logic.
## When to Define a New Monoid
Define a monoid struct if you need associative combination + identity:
- Counters, tallies, or scores
- Config merges (e.g. left-biased / right-biased maps)
- "Best-of" or "min-by/max-by" selections
- Predicate or decision combination
Expose it through a utility module—application code should not use it raw.
## Built-in Instances
- `%Funx.Monoid.Sum{}` — numeric sum (`0`)
- `%Funx.Monoid.Product{}` — numeric product (`1`)
- `%Funx.Monoid.Max{}` — maximum (`Float.min_finite()`)
- `%Funx.Monoid.Min{}` — minimum (`Float.max_finite()`)
- `%Funx.Monoid.ListConcat{}` — list concatenation (`[]`)
- `%Funx.Monoid.StringConcat{}` — string concatenation (`""`)
- `%Funx.Monoid.Predicate.All{}` — predicate AND composition (identity: `fn _ -> true end`)
- `%Funx.Monoid.Predicate.Any{}` — predicate OR composition (identity: `fn _ -> false end`)
- `%Funx.Monoid.Eq.All{}` / `%Funx.Monoid.Eq.Any{}` — equality composition
- `%Funx.Monoid.Ord{}` — ordering composition
These back the higher-level helpers. Use `Math`, `Eq.Utils`, `Ord.Utils`, or `Predicate` instead.
## LLM Code Templates
### Basic Monoid Usage Template
```elixir
defmodule DataAggregator do
import Funx.Monoid
alias Funx.Math
# Use high-level utilities instead of raw monoids
def analyze_numbers(numbers) do
%{
sum: Math.sum(numbers), # Uses Sum monoid internally
product: Math.product(numbers), # Uses Product monoid internally
maximum: Math.max(numbers), # Uses Max monoid internally
minimum: Math.min(numbers) # Uses Min monoid internally
}
end
# Custom combination using monoid utilities
def combine_stats(stat_list) do
stat_list
|> Enum.map(&extract_numbers/1)
|> Enum.reduce(fn nums1, nums2 ->
%{
sum: Math.sum([nums1.sum, nums2.sum]),
product: Math.product([nums1.product, nums2.product]),
max: Math.max([nums1.max, nums2.max]),
min: Math.min([nums1.min, nums2.min])
}
end)
end
end
```
### Custom Monoid Implementation Template
```elixir
defmodule UserPreferences do
defstruct theme: :light, notifications: true, language: "en"
end
# Custom monoid for merging user preferences (right-biased)
defmodule Funx.Monoid.UserPreferences do
defstruct []
defimpl Funx.Monoid do
def empty(_), do: %UserPreferences{}
def append(_, prefs1, prefs2) do
# Right-biased merge: prefs2 overwrites prefs1 for non-nil values
%UserPreferences{
theme: prefs2.theme || prefs1.theme,
notifications: if(is_nil(prefs2.notifications), do: prefs1.notifications, else: prefs2.notifications),
language: prefs2.language || prefs1.language
}
end
def wrap(_, prefs), do: prefs
def unwrap(prefs), do: prefs
end
end
defmodule PreferencesManager do
alias Funx.Monoid.Utils, as: MU
def merge_user_preferences(preference_list) do
# Use monoid to combine multiple preference objects
MU.m_concat(%Funx.Monoid.UserPreferences{}, preference_list)
end
def merge_with_defaults(user_prefs, defaults) do
# Combine with defaults using monoid
MU.m_append(%Funx.Monoid.UserPreferences{}, defaults, user_prefs)
end
end
```
### Monoid Law Verification Template
```elixir
defmodule MonoidLawTester do
import Funx.Monoid
# Generic test for any monoid implementation
def verify_monoid_laws(monoid_module, test_values) do
[a, b, c] = test_values
m = struct(monoid_module)
# Left Identity: empty + a = a
left_identity = append(m, empty(m), a) == a
# Right Identity: a + empty = a
right_identity = append(m, a, empty(m)) == a
# Associativity: (a + b) + c = a + (b + c)
left_assoc = append(m, append(m, a, b), c)
right_assoc = append(m, a, append(m, b, c))
associativity = left_assoc == right_assoc
%{
left_identity: left_identity,
right_identity: right_identity,
associativity: associativity,
all_laws_hold: left_identity && right_identity && associativity
}
end
# Test built-in monoids
def test_built_in_monoids() do
# Test Sum monoid
sum_result = verify_monoid_laws(Funx.Monoid.Sum, [5, 3, 8])
IO.inspect(sum_result, label: "Sum monoid laws")
# Test Product monoid
product_result = verify_monoid_laws(Funx.Monoid.Product, [2, 3, 4])
IO.inspect(product_result, label: "Product monoid laws")
# Test List concatenation
list_result = verify_monoid_laws(Funx.Monoid.ListConcat, [[1, 2], [3], [4, 5]])
IO.inspect(list_result, label: "ListConcat monoid laws")
end
end
```
### Parallel Computation with Monoids Template
```elixir
defmodule ParallelProcessor do
alias Funx.Math
# Monoids enable safe parallel computation due to associativity
def parallel_sum(large_list) do
large_list
|> Enum.chunk_every(1000) # Split into chunks
|> Task.async_stream(&Math.sum/1, max_concurrency: System.schedulers())
|> Enum.map(fn {:ok, partial_sum} -> partial_sum end)
|> Math.sum() # Combine partial results
end
def parallel_statistics(data_chunks) do
# Process chunks in parallel, then combine results
stats = data_chunks
|> Task.async_stream(fn chunk ->
%{
count: length(chunk),
sum: Math.sum(chunk),
max: Math.max(chunk),
min: Math.min(chunk)
}
end, max_concurrency: System.schedulers())
|> Enum.map(fn {:ok, stat} -> stat end)
# Combine partial statistics using monoid properties
%{
total_count: Math.sum(Enum.map(stats, & &1.count)),
total_sum: Math.sum(Enum.map(stats, & &1.sum)),
overall_max: Math.max(Enum.map(stats, & &1.max)),
overall_min: Math.min(Enum.map(stats, & &1.min))
}
end
end
```
### Utils Integration Template
```elixir
defmodule MonoidWithUtils do
alias Funx.Utils
alias Funx.Math
# Create curried monoid operations
def build_aggregators() do
# Curry math operations for reuse
sum_reducer = Utils.curry_r(&Math.sum/1)
product_reducer = Utils.curry_r(&Math.product/1)
max_finder = Utils.curry_r(&Math.max/1)
# Create specialized aggregators
sum_by = fn key ->
fn data_list ->
data_list
|> Enum.map(&Map.get(&1, key))
|> Math.sum()
end
end
product_by = fn key ->
fn data_list ->
data_list
|> Enum.map(&Map.get(&1, key))
|> Math.product()
end
end
%{
sum_reducer: sum_reducer,
product_reducer: product_reducer,
max_finder: max_finder,
sum_by: sum_by,
product_by: product_by
}
end
def analyze_grouped_data(grouped_data) do
aggregators = build_aggregators()
# Apply different aggregations to different groups
for {group, data} <- grouped_data do
{group, %{
total_score: aggregators.sum_by.(:score).(data),
multiplied_weights: aggregators.product_by.(:weight).(data),
count: length(data)
}}
end
end
end
```
### Predicate Integration Template
```elixir
defmodule MonoidPredicateIntegration do
alias Funx.Predicate
alias Funx.Monoid.Utils, as: MU
alias Funx.Monoid.Predicate.{All, Any}
# Predicates use specific monoids internally for combination
def build_complex_validators() do
# Individual predicates
is_adult = fn person -> person.age >= 18 end
has_email = fn person -> String.contains?(person.email, "@") end
has_name = fn person -> String.length(person.name) > 0 end
is_verified = fn person -> person.verified end
# Combine using predicate utilities (which use Predicate.All/Any monoids internally)
# p_all uses m_concat with %All{} monoid
strict_validator = Predicate.p_all([is_adult, has_email, has_name, is_verified])
basic_validator = Predicate.p_all([has_email, has_name])
# p_any uses m_concat with %Any{} monoid
flexible_validator = Predicate.p_any([is_adult, is_verified])
%{
strict: strict_validator,
basic: basic_validator,
flexible: flexible_validator
}
end
# Show how predicates compose via specific monoid types
def demonstrate_predicate_monoid_connection() do
# These predicates use Predicate.All/Any monoids internally
predicate1 = fn x -> x > 0 end
predicate2 = fn x -> x < 100 end
predicate3 = fn x -> rem(x, 2) == 0 end
# p_all uses m_concat(%All{}, predicates) for AND combination
all_validator = Predicate.p_all([predicate1, predicate2, predicate3])
# p_any uses m_concat(%Any{}, predicates) for OR combination
any_validator = Predicate.p_any([predicate1, predicate2, predicate3])
# You could also use monoids directly (though predicates are cleaner)
manual_all = MU.m_concat(%All{}, [predicate1, predicate2, predicate3])
manual_any = MU.m_concat(%Any{}, [predicate1, predicate2, predicate3])
# Test values
test_value = 42
%{
predicate_all: all_validator.(test_value), # true (42 > 0 AND 42 < 100 AND even)
predicate_any: any_validator.(test_value), # true (42 > 0 OR 42 < 100 OR even)
manual_all: manual_all.(test_value), # Same result as predicate_all
manual_any: manual_any.(test_value) # Same result as predicate_any
}
end
# Show the monoid identities that predicates rely on
def demonstrate_predicate_monoid_laws() do
import Funx.Monoid
# All monoid: identity is function that always returns true
all_identity = empty(%All{})
IO.inspect(all_identity.(:anything), label: "All monoid identity") # true
# Any monoid: identity is function that always returns false
any_identity = empty(%Any{})
IO.inspect(any_identity.(:anything), label: "Any monoid identity") # false
# This is why p_all([]) returns true and p_any([]) returns false
empty_all = Predicate.p_all([])
empty_any = Predicate.p_any([])
%{
empty_all_result: empty_all.(:test), # true (All identity)
empty_any_result: empty_any.(:test) # false (Any identity)
}
end
end
```
## LLM Testing Guidance
### Test Monoid Laws
```elixir
defmodule MonoidTest do
use ExUnit.Case
import Funx.Monoid
# Test that custom monoids satisfy laws
test "UserPreferences monoid satisfies laws" do
prefs1 = %UserPreferences{theme: :dark, language: "en"}
prefs2 = %UserPreferences{notifications: false}
prefs3 = %UserPreferences{theme: :light, language: "es"}
monoid = %Funx.Monoid.UserPreferences{}
# Test left identity: empty + a = a
assert append(monoid, empty(monoid), prefs1) == prefs1
# Test right identity: a + empty = a
assert append(monoid, prefs1, empty(monoid)) == prefs1
# Test associativity: (a + b) + c = a + (b + c)
left_assoc = append(monoid, append(monoid, prefs1, prefs2), prefs3)
right_assoc = append(monoid, prefs1, append(monoid, prefs2, prefs3))
assert left_assoc == right_assoc
end
test "Math utilities use monoids correctly" do
numbers = [1, 2, 3, 4, 5]
# These should be equivalent to manual monoid operations
assert Math.sum(numbers) == 15
assert Math.product(numbers) == 120
# Test empty list behavior (should return identity)
assert Math.sum([]) == 0
assert Math.product([]) == 1
end
end
```
### Test Higher-Level Utilities
```elixir
test "utility functions hide monoid complexity" do
# Test that utilities work without exposing monoid details
data = [
%{score: 10, weight: 0.5},
%{score: 20, weight: 1.0},
%{score: 15, weight: 0.8}
]
total_score = data |> Enum.map(& &1.score) |> Math.sum()
assert total_score == 45
total_weight = data |> Enum.map(& &1.weight) |> Math.sum()
assert total_weight == 2.3
end
```
## LLM Debugging Tips
### Debug Monoid Operations
```elixir
def debug_monoid_combination(monoid, values) do
IO.puts("Debugging monoid: #{inspect(monoid)}")
IO.puts("Identity: #{inspect(empty(monoid))}")
# Show step-by-step combination
Enum.reduce(values, empty(monoid), fn value, acc ->
result = append(monoid, acc, value)
IO.puts("#{inspect(acc)} + #{inspect(value)} = #{inspect(result)}")
result
end)
end
# Usage:
# debug_monoid_combination(%Funx.Monoid.Sum{}, [1, 2, 3, 4])
```
### Verify Associativity for Parallel Computing
```elixir
def verify_parallel_safety(operation, data, chunk_size) do
# Sequential computation
sequential_result = operation.(data)
# Parallel computation (different groupings)
parallel_result1 = data
|> Enum.chunk_every(chunk_size)
|> Enum.map(operation)
|> operation.()
parallel_result2 = data
|> Enum.chunk_every(chunk_size * 2) # Different chunk size
|> Enum.map(operation)
|> operation.()
%{
sequential: sequential_result,
parallel1: parallel_result1,
parallel2: parallel_result2,
results_match: sequential_result == parallel_result1 &&
parallel_result1 == parallel_result2
}
end
# Test with Math.sum (which uses Sum monoid)
# verify_parallel_safety(&Math.sum/1, [1,2,3,4,5,6,7,8], 3)
```
## LLM Common Mistakes to Avoid
**❌ Don't use raw monoids in application code**
```elixir
# ❌ Wrong: manually constructing monoids
def sum_values(numbers) do
sum_monoid = %Funx.Monoid.Sum{}
Enum.reduce(numbers, Monoid.empty(sum_monoid), fn num, acc ->
Monoid.append(sum_monoid, acc, num)
end)
end
# ✅ Correct: use utility functions
def sum_values(numbers) do
Math.sum(numbers) # Much simpler and clearer
end
```
**❌ Don't ignore monoid laws**
```elixir
# ❌ Wrong: non-associative operation
defmodule BrokenMonoid do
defstruct []
defimpl Funx.Monoid do
def empty(_), do: 0
def append(_, a, b), do: a - b # Subtraction is NOT associative!
def wrap(_, x), do: x
def unwrap(x), do: x
end
end
# ✅ Correct: ensure associativity
defmodule CorrectMonoid do
defstruct []
defimpl Funx.Monoid do
def empty(_), do: 0
def append(_, a, b), do: a + b # Addition IS associative
def wrap(_, x), do: x
def unwrap(x), do: x
end
end
```
**❌ Don't use wrong identity elements**
```elixir
# ❌ Wrong: nil is not identity for addition
defmodule BadSumMonoid do
defstruct []
defimpl Funx.Monoid do
def empty(_), do: nil # Wrong! nil + 5 != 5
def append(_, a, b), do: (a || 0) + (b || 0)
def wrap(_, x), do: x
def unwrap(x), do: x
end
end
# ✅ Correct: 0 is the true identity for addition
defmodule GoodSumMonoid do
defstruct []
defimpl Funx.Monoid do
def empty(_), do: 0 # Correct! 0 + x = x
def append(_, a, b), do: a + b
def wrap(_, x), do: x
def unwrap(x), do: x
end
end
```
## Summary
`Funx.Monoid` provides the mathematical foundation for associative combination with identity. Use it to:
- **Build reusable combination logic**: Define monoids for custom data types that need merging
- **Enable parallel computation**: Monoid laws guarantee safe parallelization and chunking
- **Power utility functions**: `Math`, `Eq.Utils`, `Ord.Utils`, and `Predicate` all use monoids internally
- **Compose complex operations**: Chain monoid operations for sophisticated data processing
- **Ensure mathematical correctness**: Monoid laws provide guarantees about behavior
**Key Implementation Detail**: Unlike Haskell's separate Semigroup and Monoid typeclasses, all operations are under the single `Funx.Monoid` protocol due to Elixir's protocol limitations.
**Best Practice**: Use high-level utilities (`Math.sum/1`, `Eq.Utils.concat_all/1`) instead of raw monoid operations. Define custom monoids for domain-specific combination needs, but expose them through utility modules rather than direct protocol usage.
Remember: Monoids are about **predictable combination**. If your operation is associative and has a true identity element, it's probably a monoid and can leverage all the mathematical guarantees and optimizations that come with that structure.