Skip to content

/howto-functional-vs-imperative

Use when writing or refactoring code, before creating files - enforces separation of pure business logic (Functional Core) from side effects (Imperative Shell) using FCIS pattern with mandatory file classification

shell
$ npx -y skills add ed3dai/ed3d-plugins --skill howto-functional-vs-imperative --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.
  • You can call itInvoke it directly when you want it.
  • Slash command/howto-functional-vs-imperative
How auto-invocation works

Context preview

The summary Claude sees to decide when to auto-load this skill.

Use when writing or refactoring code, before creating files - enforces separation of pure business logic (Functional Core) from side effects (Imperative Shell) using FCIS pattern with mandatory file classification

SKILL.md

howto-functional-vs-imperative.SKILL.md
name: functional-core-imperative-shell
description: Use when writing or refactoring code, before creating files - enforces separation of pure business logic (Functional Core) from side effects (Imperative Shell) using FCIS pattern with mandatory file classification
user-invocable: false

Functional Core, Imperative Shell (FCIS)

Overview

**Core principle:** Separate pure business logic (Functional Core) from side effects (Imperative Shell). Pure functions go in one file, I/O operations in another.

**Why this matters:** Pure functions are trivial to test (no mocks needed). I/O code is isolated to thin shells. Bugs become structurally impossible when business logic has no side effects.

When to Use

**Use FCIS when:**

  • Writing any new code file
  • Refactoring existing code
  • Reviewing code for architectural decisions
  • Deciding where logic belongs

**Trigger symptoms:**

  • "Where should this function go?"
  • Creating a new file
  • Adding database calls to logic
  • Adding file I/O to calculations
  • Writing tests that need complex mocking

MANDATORY: File Classification

**YOU MUST add pattern comment to every file containing runtime behavior:**

// pattern: Functional Core
// pattern: Imperative Shell
// pattern: Mixed (needs refactoring)

**If file genuinely cannot be separated (rare), document why:**

// pattern: Mixed (unavoidable)
// Reason: [specific technical justification]
// Example: Performance-critical path where separating I/O causes unacceptable overhead

**No file with runtime behavior without classification.** If you create a file that contains functions, classes with methods, or orchestration logic without this comment, you have violated the requirement.

Exempt: Files Without Runtime Behavior

**DO NOT add pattern comments to:**

  • **Type-only files** - files exporting only types, interfaces, or type aliases (no runtime code)
  • **Constants/enum-like files** - static data declarations, no functions
  • **Barrel/index files** - re-exports only (`export * from './foo'`)
  • **Test files** - tests exercise core/shell code but aren't themselves core or shell
  • **Generated files** - machine-generated code
  • Bash/shell scripts (.sh, .bash) - inherently imperative
  • Configuration files (eslint.config.js, tsconfig.json, .env, etc.)
  • Markdown documentation (.md)
  • HTML files (.html)
  • Task runner files (justfile, Makefile, etc.)
  • Package manifests (package.json, pyproject.toml, etc.)
  • Data files (JSON, YAML, CSV, etc.)

**Note:** If an exempt file grows to include runtime logic (e.g., a "types" file gains helper functions, or a constants file gains factory functions), it crosses the threshold and MUST be classified.

**Classification applies to application source files containing runtime behavior** (functions with logic, classes with methods, I/O orchestration).

File Type Definitions

Functional Core Files

**Contains ONLY:**

  • Pure functions (same input -> same output, always)
  • Business logic, validations, calculations, transformations
  • Data structure operations
  • Logging (EXCEPTION: loggers are permitted in Functional Core)

**NEVER contains:**

  • File I/O (reading, writing files)
  • Database operations (queries, updates, connections)
  • HTTP requests or responses
  • Environment variable access
  • Date.now(), Math.random(), or other non-deterministic functions
  • State mutations outside function scope

**Logging exception:** Functions MAY accept and use loggers. For unit tests, pass no-op loggers. This is the ONLY permitted side effect in Functional Core.

**Test signature:** Simple assertions, no mocks except logger (if used).

Imperative Shell Files

**Contains ONLY:**

  • I/O operations: file system, database, HTTP, environment
  • Orchestration: gather data -> call Functional Core -> persist results
  • Error handling for I/O failures
  • Minimal business logic (coordination only)

**NEVER contains:**

  • Complex calculations
  • Business rule validations
  • Data transformations beyond format conversion

**Test signature:** Integration tests with real dependencies or test doubles.

Code Flow Pattern

1. GATHER (Shell):  Collect data from external sources
2. PROCESS (Core):  Transform input to output (pure)
3. PERSIST (Shell): Save results externally

**Every operation follows this sequence.** No exceptions.

Decision Framework

Before writing a function, ask:

digraph fcis_decision {
    "Writing a function" [shape=ellipse];
    "Can run without external dependencies?" [shape=diamond];
    "Does it coordinate I/O?" [shape=diamond];
    "Functional Core" [shape=box, style=filled, fillcolor=lightblue];
    "Imperative Shell" [shape=box, style=filled, fillcolor=lightgreen];
    "STOP: Refactor or escalate" [shape=octagon, style=filled, fillcolor=red, fontcolor=white];

    "Writing a function" -> "Can run without external dependencies?";
    "Can run without external dependencies?" -> "Functional Core" [label="yes"];
    "Can run without external dependencies?" -> "Does it coordinate I/O?" [label="no"];
    "Does it coordinate I/O?" -> "Imperative Shell" [label="yes"];
    "Does it coordinate I/O?" -> "STOP: Refactor or escalate" [label="no"];
}

**Questions to ask:**

  • Can this logic run without file system, database, network, or environment?
  • **YES** -> Functional Core
  • **NO** -> Does it coordinate I/O or contain business logic?
  • **I/O coordination** -> Imperative Shell
  • **Business logic + I/O** -> STOP. Refactor or escalate to user.

Common Mistakes and Rationalizations

| Excuse/Thought Pattern | Reality | What To Do | |------------------------|---------|------------| | "Just one file read in this calculation" | File I/O = side effect. Not Functional Core. | Extract to Shell. Pass data as parameter. | | "Database is passed as parameter, so it's pure" | Database operations are I/O. Not pure. | Move to Shell. Core receives data, not DB connection. | | "This validation needs to check if file exists" | File s

Read more
Read it on GitHub ↗

Showing the first part of this file.

Ships withed3d-plugins

This is my collection of plugins that I use on a day-to-day basis for getting stuff done with Claude Code. Most of these are development-oriented in some way or another, but also often end up being useful for other things.

Get the whole plugin, auto-invoked