Coding & Refactoringlow risk

fp-pure-functions

Write pure functions and avoid side effects for predictable, testable code

pantheon-org/tekhne·skills/software-engineering/fp-pure-functions/SKILL.md
85/ 100质量分

导入这个 Skill

选择你的 coding agent,复制项目级或个人级安装命令。

固定到平台收录的 commit
导入当前项目.agents/skills/fp-pure-functions
npx skills add https://github.com/pantheon-org/tekhne/tree/4a79b500f771a61b6b4bf63751e038649d6535bc/skills/software-engineering/fp-pure-functions -a codex -y
导入个人环境~/.agents/skills/fp-pure-functions
npx skills add https://github.com/pantheon-org/tekhne/tree/4a79b500f771a61b6b4bf63751e038649d6535bc/skills/software-engineering/fp-pure-functions -a codex -g -y
手动放置目录.agents/skills/fp-pure-functionsOfficial docs ↗
导入当前项目.claude/skills/fp-pure-functions
npx skills add https://github.com/pantheon-org/tekhne/tree/4a79b500f771a61b6b4bf63751e038649d6535bc/skills/software-engineering/fp-pure-functions -a claude-code -y
导入个人环境~/.claude/skills/fp-pure-functions
npx skills add https://github.com/pantheon-org/tekhne/tree/4a79b500f771a61b6b4bf63751e038649d6535bc/skills/software-engineering/fp-pure-functions -a claude-code -g -y
手动放置目录.claude/skills/fp-pure-functionsOfficial docs ↗
导入当前项目.agents/skills/fp-pure-functions
npx skills add https://github.com/pantheon-org/tekhne/tree/4a79b500f771a61b6b4bf63751e038649d6535bc/skills/software-engineering/fp-pure-functions -a github-copilot -y
导入个人环境~/.copilot/skills/fp-pure-functions
npx skills add https://github.com/pantheon-org/tekhne/tree/4a79b500f771a61b6b4bf63751e038649d6535bc/skills/software-engineering/fp-pure-functions -a github-copilot -g -y
手动放置目录.agents/skills/fp-pure-functionsOfficial docs ↗
导入当前项目.agents/skills/fp-pure-functions
npx skills add https://github.com/pantheon-org/tekhne/tree/4a79b500f771a61b6b4bf63751e038649d6535bc/skills/software-engineering/fp-pure-functions -a cursor -y
导入个人环境~/.cursor/skills/fp-pure-functions
npx skills add https://github.com/pantheon-org/tekhne/tree/4a79b500f771a61b6b4bf63751e038649d6535bc/skills/software-engineering/fp-pure-functions -a cursor -g -y
手动放置目录.agents/skills/fp-pure-functionsOfficial docs ↗
导入当前项目.agents/skills/fp-pure-functions
npx skills add https://github.com/pantheon-org/tekhne/tree/4a79b500f771a61b6b4bf63751e038649d6535bc/skills/software-engineering/fp-pure-functions -a gemini-cli -y
导入个人环境~/.gemini/skills/fp-pure-functions
npx skills add https://github.com/pantheon-org/tekhne/tree/4a79b500f771a61b6b4bf63751e038649d6535bc/skills/software-engineering/fp-pure-functions -a gemini-cli -g -y
Native Gemini CLIgemini skills install https://github.com/pantheon-org/tekhne.git --scope workspace --path skills/software-engineering/fp-pure-functions
手动放置目录.agents/skills/fp-pure-functionsOfficial docs ↗
⚠ 安装命令使用开源 skills CLI。执行前请检查来源、脚本和权限。
# Pure Functions and Side Effect Management

Pure functions are the foundation of functional programming. A pure function is a function where the return value is determined only by its input values, without observable side effects. This predictability makes code easier to test, reason about, and parallelize.

## Core Characteristics of Pure Functions

A pure function must satisfy two key properties:

1. **Deterministic**: Given the same inputs, it always returns the same output
2. **No Side Effects**: It doesn't modify external state or interact with the outside world

### Example: Pure vs Impure Functions (JavaScript)

```javascript
// IMPURE: Depends on external state
let discount = 0.1;
function calculatePrice(price) {
  return price - (price * discount);
}

// PURE: All inputs are parameters
function calculatePriceWithDiscount(price, discount) {
  return price - (price * discount);
}

// IMPURE: Modifies external state
let total = 0;
function addToTotal(amount) {
  total += amount;
  return total;
}

// PURE: Returns new value without mutation
function add(a, b) {
  return a + b;
}

// Usage of pure function
const currentTotal = 100;
const newTotal = add(currentTotal, 50); // 150
// currentTotal is still 100
```

### Example: Pure Functions in Python

```python
from datetime import datetime
from typing import List, Dict

# IMPURE: Uses current time (non-deterministic)
def get_greeting():
    hour = datetime.now().hour
    if hour < 12:
        return "Good morning"
    return "Good afternoon"

# PURE: Time is passed as parameter
def get_greeting_at_time(hour: int) -> str:
    if hour < 12:
        return "Good morning"
    return "Good afternoon"

# IMPURE: Modifies input list
def add_item_impure(items: List[str], item: str) -> List[str]:
    items.append(item)
    return items

# PURE: Returns new list
def add_item_pure(items: List[str], item: str) -> List[str]:
    return [*items, item]

# IMPURE: Reads from file system
def load_config():
    with open('config.json', 'r') as f:
        return json.load(f)

# PURE: Config is passed as parameter
def process_config(config: Dict) -> Dict:
    return {
        **config,
        'processed': True,
        'timestamp': config.get('timestamp', 0)
    }
```

## Isolating Side Effects

When side effects are necessary, isolate them at the boundaries:

### JavaScript: Functional Core, Imperative Shell

```javascript
// PURE CORE: Business logic
function calculateOrderTotal(items, taxRate, shippingCost) {
  const subtotal = items.reduce((sum, item) =>
    sum + (item.price * item.quantity), 0
  );
  const tax = subtotal * taxRate;
  return subtotal + tax + shippingCost;
}

function validateOrder(order, inventory) {
  const errors = [];

  for (const item of order.items) {
    const stock = inventory[item.id];
    if (!stock || stock < item.quantity) {
      errors.push(`Insufficient stock for ${item.name}`);
    }
  }

  if (order.items.length === 0) {
    errors.push('Order must contain at least one item');
  }

  return {
    valid: errors.length === 0,
    errors
  };
}

function createInvoice(order, total) {
  return {
    orderId: order.id,
    customerId: order.customerId,
    items: order.items,
    total,
    createdAt: order.timestamp
  };
}

// IMPERATIVE SHELL: Side effects isolated here
async function processOrder(orderId) {
  // Side effect: Database read
  const order = await db.orders.findById(orderId);
  const inventory = await db.inventory.getAll();

  // Pure function call
  const validation = validateOrder(order, inventory);

  if (!validation.valid) {
    // Side effect: Logging
    console.error('Order validation failed:', validation.errors);
    return { success: false, errors: validation.errors };
  }

  // Pure function call
  const total = calculateOrderTotal(
    order.items,
    order.taxRate,
    order.shippingCost
  );

  // Pure function call
  const invoice = createInvoice(order, total);

  // Side effects: Database writes
  await db.invoices.create(invoice);
  await db.orders.update(orderId, { status: 'processed' });

  // Side effect: Email
  await emailService.send(order.customerEmail, invoice);

  return { success: true, invoice };
}
```

### Python: Dependency Injection for Purity

```python
from dataclasses import dataclass
from typing import List, Dict, Callable
from datetime import datetime

@dataclass
class Order:
    id: str
    items: List[Dict]
    customer_id: str
    tax_rate: float
    shipping_cost: float

# PURE: All dependencies are parameters
def calculate_order_total(
    items: List[Dict],
    tax_rate: float,
    shipping_cost: float
) -> float:
    subtotal = sum(item['price'] * item['quantity'] for item in items)
    tax = subtotal * tax_rate
    return subtotal + tax + shipping_cost

def validate_order(order: Order, inventory: Dict[str, int]) -> Dict:
    errors = []

    for item in order.items:
        stock = inventory.get(item['id'], 0)
        if stock < item['quantity']:
            errors.append(f"Insufficient stock for {item['name']}")

    if not order.items:
        errors.append('Order must contain at least one item')

    return {
        'valid': len(errors) == 0,
        'errors': errors
    }

def create_invoice(order: Order, total: float, timestamp: datetime) -> Dict:
    return {
        'order_id': order.id,
        'customer_id': order.customer_id,
        'items': order.items,
        'total': total,
        'created_at': timestamp.isoformat()
    }

# IMPURE: But dependencies injected for testability
class OrderProcessor:
    def __init__(self, db, email_service, logger, clock: Callable[[], datetime]):
        self.db = db
        self.email_service = email_service
        self.logger = logger
        self.clock = clock  # Inject time dependency

    async def process_order(self, order_id: str) -> Dict:
        # Side effect: Database read
        order = await self.db.orders.find_by_id(order_id)
        inventory = await self.db.inventory.get_all()

        # Pure function call
        validation = validate_order(order, inventory)

        if not validation['valid']:
            # Side effect: Logging (injected)
            self.logger.error(f"Order validation failed: {validation['errors']}")
            return {'success': False, 'errors': validation['errors']}

        # Pure function calls
        total = calculate_order_total(
            order.items,
            order.tax_rate,
            order.shipping_cost
        )

        # Time is injected, making this testable
        invoice = create_invoice(order, total, self.clock())

        # Side effects: Database writes
        await self.db.invoices.create(invoice)
        await self.db.orders.update(order_id, {'status': 'processed'})

        # Side effect: Email
        await self.email_service.send(order.customer_email, invoice)

        return {'success': True, 'invoice': invoice}
```

## Referential Transparency

A pure function exhibits referential transparency: you can replace a function call with its return value without changing program behavior.

```javascript
// Pure function
function add(a, b) {
  return a + b;
}

// These are equivalent due to referential transparency
const result1 = add(2, 3) * add(4, 5);
const result2 = 5 * 9;  // Can replace function calls with values
const result3 = 45;

// Impure function (random)
function getRandomNumber() {
  return Math.random();
}

// These are NOT equivalent
const value1 = getRandomNumber() + getRandomNumber();
const value2 = 0.5 + 0.5;  // Wrong! Can't replace with specific value
```

## Testing Pure Functions

Pure functions are trivial to test:

```python
import pytest
from decimal import Decimal

# Pure function
def calculate_compound_interest(
    principal: Decimal,
    rate: Decimal,
    years: int,
    compounds_per_year: int
) -> Decimal:
    return principal * (1 + rate / compounds_per_year) ** (compounds_per_year * years)

# Tests are simple - no mocking needed
def test_compound_interest_basic():
    result = calculate_compound_interest(
        principal=Decimal('1000'),
        rate=Decimal('0.05'),
        years=1,
        compounds_per_year=12
    )
    assert result == pytest.approx(Decimal('1051.16'), rel=1e-2)

def test_compound_interest_zero_rate():
    result = calculate_compound_interest(
        principal=Decimal('1000'),
        rate=Decimal('0'),
        years=5,
        compounds_per_year=4
    )
    assert result == Decimal('1000')

def test_compound_interest_multiple_years():
    result = calculate_compound_interest(
        principal=Decimal('5000'),
        rate=Decimal('0.06'),
        years=10,
        compounds_per_year=4
    )
    assert result == pytest.approx(Decimal('9070.09'), rel=1e-2)

# No need for:
# - Database setup/teardown
# - Mocking external services
# - Clearing global state
# - Time manipulation
```

## Language-Specific Examples and Advanced Patterns

The guidance above uses JavaScript for the primary examples. For the same patterns in other languages and deeper material, see:

- [Pure Functions in Elixir and Haskell](references/other-language-examples.md)

## When to Use This Skill

- Writing business logic that requires testing
- Implementing data transformations
- Building calculation engines
- Creating reusable utility functions
- Developing concurrent or parallel systems
- Refactoring legacy code for testability
- Implementing domain models
- Building composable abstractions
- Creating predictable APIs
- Optimizing for memoization or caching

## Best Practices

1. **Make all inputs explicit** - Pass every dependency as a parameter
2. **Avoid hidden dependencies** - Don't access global variables, environment, or system state
3. **Return new values** - Never mutate input parameters
4. **Keep functions focused** - Single responsibility makes purity easier
5. **Inject time dependencies** - Pass timestamps rather than calling Date.now() or datetime.now()
6. **Use immutable data structures** - Choose languages/libraries that support immutability
7. **Separate pure from impure code** - Keep business logic pure, isolate I/O at boundaries
8. **Document side effects explicitly** - Use type systems (like Haskell's IO) or naming conventions
9. **Prefer expressions over statements** - Return values instead of modifying state
10. **Make functions total** - Handle all possible inputs, avoid throwing exceptions when possible
11. **Use pure data transformations** - Map, filter, reduce instead of loops with mutations
12. **Avoid object-oriented patterns that encourage mutation** - Prefer functional composition
13. **Test pure functions extensively** - They're easy to test, take advantage of it
14. **Use pure functions for memoization** - Cache results since output depends only on input
15. **Document assumptions** - Make preconditions clear in types or documentation

## Common Pitfalls

1. **Hidden I/O dependencies** - Reading files, environment variables, or system time
2. **Mutating input parameters** - Modifying arrays, objects, or other references
3. **Depending on closure variables** - Using variables from outer scopes that can change
4. **Non-deterministic operations** - Random numbers, timestamps, UUIDs without injection
5. **Logging inside pure functions** - Even console.log is a side effect
6. **Throwing exceptions** - Exceptions are side effects; use Result/Option types instead
7. **Accessing this or self** - Often leads to hidden state dependencies
8. **Using impure standard library functions** - Math.random(), Date.now(), etc.
9. **Relying on execution order** - Pure functions should be order-independent
10. **Database or network calls** - These are side effects that must be isolated
11. **Modifying global state** - Including class variables, singletons, or registries
12. **DOM manipulation** - Any interaction with external systems
13. **Side effects in getters** - Properties should be pure computations
14. **Premature optimization** - Don't sacrifice purity for minor performance gains
15. **Ignoring language constraints** - Some languages make purity harder; use appropriate patterns

## Resources

- "Functional Programming in JavaScript" by Luis Atencio
- "Grokking Simplicity" by Eric Normand
- "Functional Programming in Scala" by Paul Chiusano and Rúnar Bjarnason
- Haskell Wiki: Referential Transparency
- "Professor Frisby's Mostly Adequate Guide to Functional Programming"
- "Domain Modeling Made Functional" by Scott Wlaschin
- Martin Fowler: "Functional Core, Imperative Shell"
- "Clojure for the Brave and True" by Daniel Higginbotham