Skip to content

IO Side-Effects

Effect System

Effect System make side effects explicit and trackable, originating from functional programming. Instead of allowing implicit side effects, an effect system explicitly declares, tracks, and controls the side effects of operations throughout the system. This pattern establishes a disciplined approach to handling operations that interact with external systems, modify state, or otherwise have observable effects beyond pure computation.

Key Concepts

  • Effect Type: Categorization of the kind of side effect (IO, state mutation, API call)
  • Effect Description: Declaration of what an operation might do and its parameters
  • Effect Handler: Component that processes effects when they are executed
  • Effect Chain: Composition of effectful operations that preserve effect metadata
  • Effect Isolation: Containing effects to specific scopes for better control
  • Effect Tracking: Recording all effects throughout the system lifecycle

Benefits

  • Transparency: Clear documentation of what operations do and their side effects
  • Predictability: Easier reasoning about program behavior with explicit effects
  • Testability: Side effects can be mocked, inspected, or verified independently
  • Composition: Complex operations built from simple ones with controlled effects
  • Debugging: Track the source and impact of effects throughout the system
  • Separation: Cleanly separate business logic from effect implementation

Implementation Considerations

  • Performance Overhead: Balancing explicitness with runtime cost
  • Granularity: Right level of detail for effect descriptions
  • Handler Strategy: Global vs. contextual handlers for different environments
  • Effect Scope: Local vs. propagating effects across component boundaries
  • Error Handling: Strategy for effect failures and recovery mechanisms
  • Concurrency Model: How effects interact with parallel execution

Core Interfaces

Effectful[V]
├── with_effect(effect) -> Effectful[V]    # Add an effect
├── map(fn) -> Effectful[Any]              # Transform value
├── bind(fn) -> Effectful[Any]             # Chain with another effectful operation
├── run(handler) -> V                      # Execute with handler
└── get_effects() -> List[Effect]          # Inspect effects

Implementation with Effect Library

NERV implements the Effect System pattern using the Effect library, which provides a functional approach to effect handling:

Core Library Components

ComponentDescriptionNERV Integration
Effect base classBase for all effect representationsExtended for domain-specific effects
TypedEffect classType-aware effect representationUsed for strongly-typed effects
perform() functionCentral effect execution mechanismCore runtime for effect processing
sync_performer decoratorRegisters synchronous handlersUsed for most effect handlers
async_performer decoratorRegisters asynchronous handlersUsed for IO/network effects
ComposedPerformer classCombines multiple effect handlersUsed to build handler registry
TypeDispatcher classRoutes effects to correct handlersUsed for effect routing
@do decoratorProvides do-notation for effectsUsed for sequential effect composition
parallel_all() functionExecutes effects in parallelUsed for concurrent effect processing
retry() functionRetries failed effectsUsed for resilient effect execution

Core Effect Types

python
from enum import Enum, auto
from dataclasses import dataclass
from typing import Any, Dict, Optional

class EffectType(Enum):
    """Types of side effects in the system."""
    # I/O effects
    FILE_READ = auto()
    FILE_WRITE = auto()
    NETWORK_REQUEST = auto()
    DATABASE_QUERY = auto()

    # External service effects
    MODEL_CALL = auto()
    TOOL_INVOKE = auto()
    API_CALL = auto()

    # State effects
    STATE_READ = auto()
    STATE_MODIFY = auto()

@dataclass
class EffectIntent:
    """Description of an intended side effect."""
    type: EffectType
    payload: Any = None
    description: str = ""

Effect Handlers

The Effect library enables specialized handlers for different effect types:

python
from effect import sync_performer, Effect, ComposedPerformer, TypeDispatcher

# FileReadEffect handler
@sync_performer
def perform_file_read(dispatcher, effect):
    """Performer for file read operations."""
    path = effect.payload.get("path")
    mode = effect.payload.get("mode", "r")
    with open(path, mode) as f:
        return f.read()

# ApiCallEffect handler
@sync_performer
def perform_api_call(dispatcher, effect):
    """Performer for API call operations."""
    import requests
    url = effect.payload.get("url")
    method = effect.payload.get("method", "GET")
    data = effect.payload.get("data")
    return requests.request(method, url, json=data).json()

# Compose handlers into a dispatcher
dispatcher = TypeDispatcher({
    FileReadEffect: perform_file_read,
    ApiCallEffect: perform_api_call,
    # Additional handlers...
})

Integration Patterns

The Effect System integrates with other NERV patterns:

  1. With Reactive Event Mesh: Effects can emit events through the EventBus
  2. With Temporal Versioning: Effect history can be versioned in TemporalStore
  3. With State Projection: Effects can trigger state changes through projections
  4. With Quantum Partitioning: Effects can be distributed across execution units

Pattern Variations

Monadic Effects

Functional approach using monads to encapsulate and compose effects. Best for pure functional systems, with strong type safety and composition capabilities.

python
# Example monadic effect sequence
result = (
    fetch_user(user_id)
    .bind(lambda user: fetch_posts(user['id']))
    .map(lambda posts: [post['title'] for post in posts])
)

Tagged Effects

Lightweight approach using type tags or metadata to describe effects. Simpler but less powerful, better suited for smaller systems or specific domains.

python
# Example tagged effect
@effect_type("file_read")
def read_file(path):
    # Implementation...

Capability-Based Effects

Effects constrained by explicit capabilities passed to functions. Provides fine-grained control over what operations can perform which effects.

python
# Example capability-based effect
def process_data(data, io_capability=None):
    if io_capability:
        io_capability.write_file("result.txt", data)
    return process(data)

Library-Specific Implementation Details

The Effect library provides several key mechanisms used in NERV:

Effect Performers

python
# Register performers for different effect types
performers = ComposedPerformer([
    (FileReadEffect, perform_file_read),
    (ApiCallEffect, perform_api_call),
    (DatabaseEffect, perform_database_query)
])

# Execute effects with the performer
result = perform(effect, performers)

Async Effect Support

python
from effect import async_performer

@async_performer
async def perform_async_api_call(dispatcher, effect):
    """Async performer for API call operations."""
    import aiohttp
    async with aiohttp.ClientSession() as session:
        method = getattr(session, effect.method.lower())
        async with method(effect.url) as response:
            return await response.json()

Effect Composition with Do Notation

The Effect library provides a powerful “do notation” for composing multiple effects:

python
from effect.do import do

@do
def fetch_user_with_posts(user_id):
    """Compose multiple effects with the do notation."""
    user = yield fetch_user_effect(user_id)
    posts = yield fetch_posts_effect(user['id'])
    return {'user': user, 'posts': posts}

Parallel Effect Execution

The Effect library supports parallel execution of independent effects:

python
from effect import parallel_all

def fetch_dashboard_data(user_id):
    """Execute multiple effects in parallel."""
    effects = [
        user_info_effect(user_id),
        recent_posts_effect(user_id),
        notification_count_effect(user_id),
        recommended_users_effect(user_id)
    ]
    return parallel_all(effects)

Performance Considerations

Effect systems can introduce overhead, but several optimization strategies are available:

  • Effect Batching: Group similar effects to reduce handler overhead
  • Lazy Evaluation: Defer effect execution until results are needed
  • Caching: Cache results of pure operations with the same parameters
  • Effect Fusion: Combine multiple effects into a single operation where possible
  • Selective Tracking: Only track effects that are relevant to current context

Integration with Atlas

The Effect System integrates with multiple Atlas subsystems:

  1. Provider System: Tracking API calls to LLM providers

    python
    fetch_completion = provider_request_effect(
        provider="anthropic",
        model="claude-3-haiku",
        prompt="Generate a story"
    )
  2. Knowledge System: Monitoring database operations and document storage

    python
    retrieve_docs = database_query_effect(
        collection="documents",
        query={"keywords": ["architecture", "patterns"]}
    )
  3. Agent System: Tracking agent actions and decision processes

    python
    agent_action = agent_effect(
        agent_id="researcher",
        action="search",
        parameters={"query": "NERV architecture"}
    )
  4. Tool System: Recording tool invocations and their effects

    python
    tool_result = tool_invoke_effect(
        tool_name="web_search",
        parameters={"query": "effect system pattern"}
    )
  5. File System: Monitoring file operations and resource usage

    python
    file_content = file_read_effect(
        path="/docs/architecture.md"
    )

Released under the MIT License.