Functional Programming Principles

We don't enforce a functional programming paradigm. We steal what works from it. The core idea: build systems where the same inputs always produce the same outputs. No hidden state, no surprise side effects, no mystery behavior.

Purity

    PURE                           IMPURE
    ────                           ──────
   ┌─────────┐                   ┌─────────┐
   │         │                   │         │◄──── global state
 ──┤  f(x)   ├──►                │  f(x)   │
   │         │                   │         ├────► side effects
   └─────────┘                   └─────────┘

 Same input = Same output       Who knows?
# ✗ Impure - mutates input, uses external state
tax_rate = 0.1
def add_tax(prices):
    for i in range(len(prices)):
        prices[i] *= (1 + tax_rate)

# ✓ Pure - new output from explicit input
def add_tax(prices, tax_rate):
    return [p * (1 + tax_rate) for p in prices]

Dependency Injection is Functional

No hidden inputs — pass everything a function needs. DI says the same thing at the class level.

  FUNCTIONAL                          CLASS-BASED (DI)
  ──────────                          ────────────────

  def deactivate(user_repo, id):      class UserService:
      user = user_repo.find(id)           def __init__(self, user_repo):
      ...                                     self.user_repo = user_repo

                                          def deactivate(self, id):
  ↑ Every dependency is an argument           user = self.user_repo.find(id)
                                              ...

                                      ↑ Every dependency is injected
                                        Methods use what the class declared

Same principle, different scope. The class constructor is the declaration of "what I need." Methods freely use those declared dependencies — they aren't hidden, they're the class's explicit contract. A function that takes user_repo as an argument and a class that takes user_repo in its constructor are both saying: I need this, give it to me.

Composition

  f(x)          g(x)              g(f(x))
 ┌─────┐       ┌─────┐           ┌─────────────┐
 │ A→B │   +   │ B→C │     =     │    A→C      │
 └─────┘       └─────┘           └─────────────┘

 Small, focused functions combine into complex transformations
# Small, pure functions
def parse(s):    return int(s)
def double(n):   return n * 2
def to_string(n): return f"Result: {n}"

# Composed
def process(s):  return to_string(double(parse(s)))

# Or piped
process = parse(s)
  |> double
  |> to_string

State Management

  EXPLICIT                        IMPLICIT
  ────────                        ────────
  ┌─────────────────┐            ┌─────────────────┐
  │ {                │            │                 │
  │   user: {...},   │            │     f(x)        │ ← reads config?
  │   config: {...}  │───►f(x)    │                 │ ← session state?
  │ }                │            │                 │ ← who knows?
  └─────────────────┘            └─────────────────┘

  Everything needed is passed     Hidden dependencies
# ✗ Implicit - assumes user exists, uses global
def get_greeting():
    return f"Hello, {current_user.name}"

# ✓ Explicit - all inputs declared, absence modeled
def get_greeting(user):
    return f"Hello, {user.name}" if user else "Hello, guest"

Transformation

  DECLARATIVE                     IMPERATIVE
  ───────────                     ──────────

  results = [u.email              results = []
    for u in users                for u in users:
    if u.active]                      if u.active:
                                          results.append(u.email)

  ↓                               ↓
  What we want                    How to do it step by step
# Declarative transformations
active_emails = [u.email for u in users if u.active]

active_emails = users.filter(u => u.active).map(u => u.email)

total = sum(item.price for item in items)

grouped = group_by(items, key=u => u.category)