ワンクリックで
functional
Functional programming patterns with immutable data. Use when writing logic or data transformations.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
Functional programming patterns with immutable data. Use when writing logic or data transformations.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
Build and sharpen a project's domain model. Use when the user wants to pin down domain terminology or a ubiquitous language, record an architectural decision, or when another skill needs to maintain the domain model.
Craft rules for technical documentation prose — structure, voice, headings, steps, tables, callouts, and forbidden filler. Use when writing, reviewing, or revising developer docs (guides, tutorials, how-tos, reference, concept pages, READMEs). Pairs with the diataxis skill: diataxis decides *what kind* of page this is, this skill governs *how each sentence lands*.
SOC 職業分類に基づく
| name | functional |
| description | Functional programming patterns with immutable data. Use when writing logic or data transformations. |
Immutable data is the foundation of functional programming. Understanding WHY helps you embrace it:
Example of the problem:
// ❌ WRONG - Mutation creates unpredictable behavior
const user = { name: "Alice", permissions: ["read"] };
grantPermission(user, "write"); // Mutates user.permissions internally
console.log(user.permissions); // ['read', 'write'] - SURPRISE! user changed
// ✅ CORRECT - Immutable approach is predictable
const user = { name: "Alice", permissions: ["read"] };
const updatedUser = grantPermission(user, "write"); // Returns new object
console.log(user.permissions); // ['read'] - original unchanged
console.log(updatedUser.permissions); // ['read', 'write'] - new version
We follow "Functional Light" principles - practical functional patterns without heavy abstractions:
What we DO:
What we DON'T do:
Why: The goal is maintainable, testable code - not academic purity. If a functional pattern makes code harder to understand, don't use it.
Example - Keep it simple:
// ✅ GOOD - Simple, clear, functional
const activeUsers = users.filter((user) => user.active);
const userNames = activeUsers.map((user) => user.name);
// ❌ OVER-ENGINEERED - Unnecessary abstraction
function compose<T>(...fns: Array<(argument: T) => T>) {
return (x: T) => fns.reduceRight((value, func) => func(value), x);
}
const activeUsers = compose(
filter((user: User) => user.active),
map((user: User) => user.name),
)(users);
Code should be clear through naming and structure. Comments indicate unclear code.
Exception: JSDoc for public APIs when generating documentation.
❌ WRONG - Comments explaining unclear code
// Get the user and check if active and has permission
function check(user: unknown): boolean {
return !!(
user && // Check user exists
user.a && // Check if active
user.p // Check permission
);
}
✅ CORRECT - Self-documenting code
function canUserAccessResource(user: undefined | User): boolean {
if (!user) {
return false;
}
if (!user.isActive) {
return false;
}
if (!user.hasPermission) {
return false;
}
return true;
}
// Even better - compose predicates
function canUserAccessResource(user: undefined | User): boolean {
return user?.isActive && user?.hasPermission;
}
If code requires comments to understand, refactor instead:
✅ Acceptable JSDoc for public APIs
/**
* Registers a scenario for runtime switching.
*
* @param definition - The scenario configuration including mocks and metadata.
* @throws {ValidationError} - If scenario ID is duplicate.
*/
export function registerScenario(definition: ScenaristScenario): void {
// Implementation
}
Prefer map, filter, reduce for transformations. They're declarative (what,
not how) and naturally immutable.
❌ WRONG - Imperative loop
const scenarioIds = [];
for (const scenario of scenarios) {
scenarioIds.push(scenario.id);
}
✅ CORRECT - Functional map
const scenarioIds = scenarios.map((scenario) => scenario.id);
❌ WRONG - Imperative loop
const activeScenarios = [];
for (const scenario of scenarios) {
if (scenario.active) {
activeScenarios.push(scenario);
}
}
✅ CORRECT - Functional filter
const activeScenarios = scenarios.filter((scenario) => scenario.active);
❌ WRONG - Imperative loop
let total = 0;
for (const item of items) {
total += item.price * item.quantity;
}
✅ CORRECT - Functional reduce
const total = items.reduce((sum, item) => sum + item.price * item.quantity, 0);
✅ CORRECT - Compose array methods
const total = items
.filter((item) => item.active)
.map((item) => item.price * item.quantity)
.reduce((sum, price) => sum + price, 0);
Imperative loops are fine when:
for...of with break)But even then, consider:
Array.find() for early terminationArray.some() / Array.every() for boolean checksDefault to options objects for function parameters. This improves readability and reduces ordering dependencies.
Benefits:
❌ WRONG - Positional parameters
// eslint-disable-next-line better-max-params/better-max-params -- Example of poor parameter usage
function createPayment(
amount: number,
currency: string,
cardId: string,
cvv: string,
saveCard: boolean,
sendReceipt: boolean,
): Payment {
// ...
}
// Call site - unclear what parameters mean
createPayment(100, "GBP", "card_123", "123", true, false);
✅ CORRECT - Options object
interface CreatePaymentOptions {
amount: number;
cardId: string;
currency: string;
cvv: string;
saveCard?: boolean;
sendReceipt?: boolean;
}
function createPayment(options: CreatePaymentOptions): Payment {
const {
amount,
cardId,
currency,
cvv,
saveCard = false,
sendReceipt = true,
} = options;
// ...
}
// Call site - crystal clear
createPayment({
amount: 100,
cardId: "card_123",
currency: "GBP",
cvv: "123",
saveCard: true,
});
Use positional parameters when:
add(a, b))// ✅ OK - Obvious ordering, few parameters
function add(a: number, b: number): number {
return a + b;
}
function updateUser(user: User, changes: Partial<User>): User {
return { ...user, ...changes };
}
Pure functions have no side effects and always return the same output for the same input.
No side effects
Deterministic
Referentially transparent
❌ WRONG - Impure function (mutations)
function addScenario(scenarios: Array<Scenario>, scenarioNew: Scenario): void {
scenarios.push(scenarioNew); // ❌ Mutates input
}
let count = 0;
function increment(): number {
count++; // ❌ Modifies external state
return count;
}
✅ CORRECT - Pure functions
function addScenario(
scenarios: ReadonlyArray<Scenario>,
scenarioNew: Scenario,
): ReadonlyArray<Scenario> {
return [...scenarios, scenarioNew]; // ✅ Returns new array
}
function increment(count: number): number {
return count + 1; // ✅ No external state
}
Some functions must be impure (I/O, randomness, side effects). Isolate them:
// ✅ CORRECT - Isolate impure functions at edges
// Pure core
function calculateTotal(items: ReadonlyArray<Item>): number {
return items.reduce((sum, item) => sum + item.price, 0);
}
// Impure shell (isolated)
async function saveOrder(order: Order): Promise<void> {
const total = calculateTotal(order.items); // Pure
await database.save({ ...order, total }); // Impure (I/O)
}
Pattern: Keep impure functions at system boundaries (adapters, ports). Keep core domain logic pure.
Compose small functions into larger ones. Each function does one thing well.
❌ WRONG - Complex monolithic function
function registerScenario(input: unknown): void {
if (typeof input !== "object" || !input) {
throw new Error("Invalid input");
}
if (!("id" in input) || typeof input.id !== "string") {
throw new Error("Missing id");
}
if (!("name" in input) || typeof input.name !== "string") {
throw new Error("Missing name");
}
if (!("mocks" in input) || !Array.isArray(input.mocks)) {
throw new Error("Missing mocks");
}
// ... 50 more lines of validation and registration
}
✅ CORRECT - Composed functions
function register(scenario: Scenario): void {
registry.register(scenario);
}
// Compose them
function registerScenario(input: unknown): void {
register(validate(input));
}
// Small, focused functions
function validate(input: unknown): Scenario {
return ScenarioSchema.parse(input);
}
// Even better - use pipe/compose utilities
const registerScenario = pipe(validate, register);
// Small transformation functions
function addDiscount(order: Order, percent: number): Order {
return { ...order, total: order.total * (1 - percent / 100) };
}
function addShipping(order: Order, cost: number): Order {
return { ...order, total: order.total + cost };
}
function addTax(order: Order, rate: number): Order {
return { ...order, total: order.total * (1 + rate) };
}
// Compose them
function finalizeOrder(order: Order): Order {
return addTax(addShipping(addDiscount(order, 10), 5.99), 0.2);
}
Use readonly on all data structures to signal immutability intent.
// ✅ CORRECT - Immutable data structure
interface Scenario {
readonly id: string;
readonly name: string;
readonly description: string;
}
// ❌ WRONG - Mutable
interface Scenario {
id: string;
name: string;
}
// ✅ CORRECT - Immutable array
interface Scenario {
readonly mocks: ReadonlyArray<Mock>;
}
// ❌ WRONG - Mutable array
interface Scenario {
readonly mocks: Array<Mock>;
}
// ✅ CORRECT - Deep immutability
interface Mock {
readonly method: "GET" | "POST";
readonly response: {
readonly body: ReadonlyArray<unknown>;
readonly status: number;
};
}
Max 2 levels of function nesting. Beyond that, extract functions.
❌ WRONG - Deep nesting (4+ levels)
function processOrder(order: Order): void {
if (
order.items.length > 0 &&
order.customer.verified &&
order.total > 0 &&
order.payment.valid
) {
// ... deeply nested logic
}
}
✅ CORRECT - Flat with early returns
function processOrder(order: Order): void {
if (order.items.length === 0) {
return;
}
if (!order.customer.verified) {
return;
}
if (order.total <= 0) {
return;
}
if (!order.payment.valid) {
return;
}
// Main logic at top level
processValidOrder(order);
}
✅ CORRECT - Extract to functions
function canProcessOrder(order: Order): boolean {
return (
order.items.length > 0 &&
order.customer.verified &&
order.total > 0 &&
order.payment.valid
);
}
async function processOrder(order: Order): Promise<Result<OrderData>> {
if (!canProcessOrder(order)) {
return { error: new Error("Cannot process order"), success: false };
}
const validated = validateOrder(order);
return executeOrder(validated);
}
Complete catalog of array mutations and their immutable alternatives:
// ❌ WRONG - Mutations
items.push(newItem); // Add to end
items.pop(); // Remove last
items.unshift(newItem); // Add to start
items.shift(); // Remove first
items.splice(index, 1); // Remove at index
items.reverse(); // Reverse order
items.sort(); // Sort
items[i] = value; // Update at index
// ✅ CORRECT - Immutable alternatives
const withNew = [...items, item]; // Add to end
const withoutLast = items.slice(0, -1); // Remove last
const withFirst = [item, ...items]; // Add to start
const withoutFirst = items.slice(1); // Remove first
const removed = [
...items.slice(0, index), // Remove at index
...items.slice(index + 1),
];
const reversed = [...items].reverse(); // Reverse (copy first!)
const sorted = [...items].sort(); // Sort (copy first!)
const updated = items.map(
(
item,
index, // Update at index
) => {
return index === i ? value : item;
},
);
Common patterns:
// Filter out specific item
const withoutItem = items.filter((item) => item.id !== targetId);
// Replace specific item
const replaced = items.map((item) =>
item.id === targetId ? updatedItem : item,
);
// Insert at specific position
const inserted = [...items.slice(0, index), updatedItem, ...items.slice(index)];
// ❌ WRONG
user.name = "New";
Object.assign(user, { name: "New" });
// ✅ CORRECT
const updated = { ...user, name: "New" };
// ✅ CORRECT - Immutable nested update
const updatedCart = {
...cart,
items: cart.items.map((item, index) => {
return index === targetIndex
? { ...item, quantity: updatedQuantity }
: item;
}),
};
// ✅ CORRECT - Immutable nested array update
const updatedOrder = {
...order,
items: [
...order.items.slice(0, index),
updatedItem,
...order.items.slice(index + 1),
],
};
// ❌ WRONG - Nested conditions
if (user && user.isActive && user.hasPermission) {
// do something
}
// ✅ CORRECT - Early returns (guard clauses)
if (!user) {
return;
}
if (!user.isActive) {
return;
}
if (!user.hasPermission) {
return;
}
// do something
console.log("User is valid and has permission");
// ✅ CORRECT - Isolate try/catch around external calls
interface TryCatchError {
err: unknown;
success: false;
}
type TryCatchResult<T> = TryCatchError | TryCatchSuccess<T>;
interface TryCatchSuccess<T> {
data: T;
success: true;
}
export async function tryCatch<T>(
operation: () => Promise<T>,
): Promise<TryCatchResult<T>> {
try {
const data = await operation();
return { data, success: true };
} catch (err) {
return { err, success: false };
}
}
// Usage
const result = await tryCatch(() => externalApi.call());
if (!result.success) {
if (result.error instanceof MyError) {
// handle MyError
}
return;
}
// TypeScript knows result.data exists here
console.log(result.data);
// Usage
async function processPayment(payment: Payment): Promise<Result<Transaction>> {
if (payment.amount <= 0) {
return { error: new PaymentError("Invalid amount"), success: false };
}
const transaction = await executePayment(payment);
return { data: transaction, success: true };
}
// Caller handles both cases explicitly
const result = await processPayment(payment);
if (!result.success) {
return;
}
// TypeScript knows result.data exists here
console.log(result.data.transactionId);
When writing functional code, verify:
map, filter, reduce) over loopsreadonly on all data structure propertiesReadonlyArray<T> for immutable arrays