원클릭으로
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