함수형 프로그래밍 — 순수 함수, 커링, 합성, 모나드 맛보기
자바스크립트는 함수가 일급 객체(First-Class Citizen)이므로 함수형 프로그래밍 스타일을 자연스럽게 적용할 수 있습니다. 면접에서 "순수 함수가 뭔가요?"라는 질문이 나오면, 사이드 이펙트와 참조 투명성을 언급할 수 있어야 합니다.
순수 함수 (Pure Function)
같은 입력에 항상 같은 출력을 반환하고, 사이드 이펙트가 없는 함수입니다.
// 순수 함수
function add(a, b) {
return a + b;
}
function getFullName(firstName, lastName) {
return `${firstName} ${lastName}`;
}
// 비순수 함수 — 외부 상태에 의존
let count = 0;
function increment() {
count++; // 외부 변수 변경 (사이드 이펙트)
return count;
}
// 비순수 함수 — 매번 다른 결과
function getRandom() {
return Math.random(); // 같은 입력인데 다른 출력
}
// 비순수 → 순수로 변환
function addToArray(arr, item) {
return [...arr, item]; // 원본 변경 없이 새 배열 반환
}
순수 함수의 장점
- **예측 가능 **: 같은 입력 → 같은 출력
- ** 테스트 용이 **: 외부 의존성 없음
- ** 캐싱 가능 **: 메모이제이션 적용 가능
- ** 병렬 처리 안전 **: 공유 상태 없음
커링 (Currying)
여러 인자를 받는 함수를 단일 인자 함수의 연쇄로 변환합니다.
// 일반 함수
function multiply(a, b) {
return a * b;
}
// 커링된 함수
function curriedMultiply(a) {
return function (b) {
return a * b;
};
}
// 화살표 함수로 더 간결하게
const curriedMultiply2 = (a) => (b) => a * b;
const double = curriedMultiply2(2);
const triple = curriedMultiply2(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15
범용 커링 함수
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn(...args);
}
return (...moreArgs) => curried(...args, ...moreArgs);
};
}
const add = curry((a, b, c) => a + b + c);
add(1)(2)(3); // 6
add(1, 2)(3); // 6
add(1)(2, 3); // 6
add(1, 2, 3); // 6
실전 활용
// 로그 함수 커링
const log = curry((level, prefix, message) => {
console.log(`[${level}] ${prefix}: ${message}`);
});
const errorLog = log("ERROR");
const apiError = errorLog("API");
apiError("요청 실패"); // "[ERROR] API: 요청 실패"
// 이벤트 핸들러
const handleEvent = curry((type, handler, event) => {
if (event.type === type) handler(event);
});
const handleClick = handleEvent("click");
함수 합성 (Composition)
여러 함수를 조합하여 새 함수를 만듭니다.
// compose — 오른쪽에서 왼쪽으로 실행
const compose = (...fns) => (x) =>
fns.reduceRight((acc, fn) => fn(acc), x);
// pipe — 왼쪽에서 오른쪽으로 실행 (더 직관적)
const pipe = (...fns) => (x) =>
fns.reduce((acc, fn) => fn(acc), x);
// 사용
const trim = (str) => str.trim();
const toLowerCase = (str) => str.toLowerCase();
const split = (sep) => (str) => str.split(sep);
const processInput = pipe(trim, toLowerCase, split(" "));
processInput(" Hello World "); // ["hello", "world"]
// 데이터 변환 파이프라인
const processUsers = pipe(
(users) => users.filter((u) => u.active),
(users) => users.map((u) => u.name),
(names) => names.sort()
);
고차 함수 (Higher-Order Function)
함수를 인자로 받거나 함수를 반환하는 함수입니다.
// 함수를 반환
function withLogging(fn) {
return function (...args) {
console.log(`호출: ${fn.name}(${args.join(", ")})`);
const result = fn(...args);
console.log(`결과: ${result}`);
return result;
};
}
const loggedAdd = withLogging((a, b) => a + b);
loggedAdd(2, 3);
// 호출: (2, 3)
// 결과: 5
// 메모이제이션
function memoize(fn) {
const cache = new Map();
return function (...args) {
const key = JSON.stringify(args);
if (cache.has(key)) return cache.get(key);
const result = fn(...args);
cache.set(key, result);
return result;
};
}
const fibonacci = memoize((n) => {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
});
모나드 맛보기 — Maybe
null/undefined를 안전하게 처리하는 패턴입니다.
class Maybe {
constructor(value) {
this.value = value;
}
static of(value) {
return new Maybe(value);
}
map(fn) {
return this.value == null ? this : Maybe.of(fn(this.value));
}
flatMap(fn) {
return this.value == null ? this : fn(this.value);
}
getOrElse(defaultValue) {
return this.value ?? defaultValue;
}
}
// null 안전한 체이닝
const userName = Maybe.of(user)
.map((u) => u.profile)
.map((p) => p.name)
.map((n) => n.toUpperCase())
.getOrElse("익명");
// user가 null이어도 에러 없이 "익명" 반환
함수형 vs 명령형
// 명령형 — HOW를 기술
function getAdultNames(users) {
const result = [];
for (let i = 0; i < users.length; i++) {
if (users[i].age >= 18) {
result.push(users[i].name.toUpperCase());
}
}
return result;
}
// 함수형 — WHAT을 기술
const getAdultNames = (users) =>
users
.filter((u) => u.age >= 18)
.map((u) => u.name.toUpperCase());
**기억하기 **: 순수 함수는 "같은 입력, 같은 출력, 사이드 이펙트 없음"입니다. 커링은 인자를 하나씩 받아 재사용 가능한 함수를 만들고, 합성(pipe/compose)은 작은 함수를 조합해 복잡한 로직을 구성합니다. 자바스크립트에서는 map, filter, reduce가 이미 함수형 프로그래밍입니다.
댓글 로딩 중...