Skip to content

Functional Programming Patterns

Kompoz combinators map naturally to common functional programming idioms. This guide demonstrates eight patterns using an order-processing pipeline.

Domain Model

All examples use a frozen (immutable) dataclass:

from dataclasses import dataclass, replace

@dataclass(frozen=True)
class Order:
    items: list[str]
    subtotal: float = 0.0
    tax: float = 0.0
    total: float = 0.0
    discount: float = 0.0
    status: str = "pending"

1. Pipeline Composition

Chain transforms with & to build left-to-right data pipelines:

from kompoz import pipe, pipe_args

@pipe
def calculate_subtotal(order: Order) -> Order:
    return replace(order, subtotal=len(order.items) * 9.99)

@pipe_args
def apply_tax(order: Order, rate: float) -> Order:
    return replace(order, tax=round(order.subtotal * rate, 2))

@pipe
def compute_total(order: Order) -> Order:
    return replace(order, total=round(order.subtotal + order.tax - order.discount, 2))

# Each step feeds the next
checkout = calculate_subtotal & apply_tax(0.08) & compute_total

2. Railway-Oriented Programming

Failures short-circuit the pipeline automatically:

from kompoz import rule

@rule
def has_items(order: Order) -> bool:
    return len(order.items) > 0

@pipe
def validate_inventory(order: Order) -> Order:
    if "out_of_stock" in order.items:
        raise ValueError("Item unavailable")
    return order

# If any step fails the rest is skipped
safe_checkout = has_items & validate_inventory & checkout
['a', 'b']                    -> total=$21.58
[]                             -> FAILED (short-circuited)
['a', 'out_of_stock']         -> FAILED (short-circuited)

3. Fallback Chains

Use | to try alternatives -- the first success wins:

@pipe
def from_cache(order: Order) -> Order:
    raise KeyError("cache miss")

@pipe
def from_primary(order: Order) -> Order:
    raise ConnectionError("primary down")

@pipe
def from_fallback(order: Order) -> Order:
    return replace(order, tax=5.0)

# First success wins, rest are skipped
resolve_tax = from_cache | from_primary | from_fallback

4. Conditional Branching

Use if_then_else for explicit branching -- exactly one branch runs:

from kompoz import if_then_else

@rule
def is_premium(order: Order) -> bool:
    return len(order.items) >= 5

@pipe
def apply_premium_discount(order: Order) -> Order:
    return replace(order, discount=round(order.subtotal * 0.20, 2))

@pipe
def no_discount(order: Order) -> Order:
    return replace(order, discount=0.0)

apply_pricing = if_then_else(is_premium, apply_premium_discount, no_discount)

5. Higher-Order Combinators

Use parameterized factories (@rule_args, @pipe_args) for partially applied combinators:

from kompoz import rule_args, pipe_args

@rule_args
def min_order_value(order: Order, threshold: float) -> bool:
    return order.subtotal >= threshold

@pipe_args
def add_flat_fee(order: Order, fee: float) -> Order:
    return replace(order, total=order.total + fee)

# Partially apply to create specialized combinators
qualifies_for_free_shipping = min_order_value(50.0)
add_shipping = add_flat_fee(5.99)

6. Pure Error Handling

Use run_with_error() instead of checking last_error for thread-safe error access:

@pipe
def parse_quantity(raw: str) -> str:
    n = int(raw)
    if n <= 0:
        raise ValueError("quantity must be positive")
    return raw

for raw in ["5", "abc", "-1"]:
    ok, result, error = parse_quantity.run_with_error(raw)
    if ok:
        print(f"  {raw!r} -> ok")
    else:
        print(f"  {raw!r} -> error: {error}")
  '5'   -> ok
  'abc' -> error: invalid literal for int() with base 10: 'abc'
  '-1'  -> error: quantity must be positive

7. Retry as a Combinator

Wrap flaky operations with Retry:

from kompoz import Retry

@pipe
def flaky_service(order: Order) -> Order:
    # Simulates a service that fails intermittently
    ...

resilient_confirm = Retry(flaky_service, max_attempts=5, backoff=0.0)
info = resilient_confirm.run_with_info(order)
print(f"ok={info.ok}, attempts={info.attempts_made}")

8. Full Pipeline

Combine all patterns into a complete pipeline:

full_pipeline = (
    has_items              # gate: must have items
    & validate_inventory   # gate: all items in stock
    & calculate_subtotal   # transform: compute subtotal
    & apply_pricing        # branch: premium vs standard discount
    & apply_tax(0.08)      # transform: add tax
    & compute_total        # transform: final total
)

Tip

See the full runnable example in examples/functional_example.py.