
자바스크립트로 함수형 프로그래밍 핸드북
핵심 요약 자바스크립트는 일급 함수(first-class function)와 고계 함수(higher-order function)를 지원하여, 함수형 프로그래밍 패러다임을 효과적으로 구현할 수 있다. ES6 이후 추가된 화살표 함수, 스프레드 연산자, 구조 분해 할당 등의 기능들이 함수형 프로그래밍을 더욱 간결하고 표현력 있게 만든다12.
1. 자바스크립트 함수형 프로그래밍의 배경
1.1 자바스크립트의 특성
자바스크립트는 멀티패러다임 언어로, 객체지향·절차형·함수형 프로그래밍을 모두 지원한다3. 특히 다음과 같은 특성들이 함수형 프로그래밍을 가능하게 한다:
- 일급 함수: 함수를 변수에 할당, 인자로 전달, 반환값으로 사용 가능2
- 고계 함수: 함수를 인자로 받거나 반환하는 함수 지원45
- 클로저: 외부 함수의 변수에 접근할 수 있는 내부 함수67
- 동적 타이핑: 런타임에 타입 결정으로 유연한 함수 조합 가능
1.2 ES6의 함수형 프로그래밍 강화
ES6(ES2015)는 함수형 프로그래밍 구현을 크게 개선했다89:
기능 | 설명 | 함수형 프로그래밍 기여도 |
---|---|---|
화살표 함수 | (x) => x * 2 간결한 함수 표현 | 고계 함수 작성 용이성 증대 |
const /let | 블록 스코프 변수 선언 | 불변성 강화 |
스프레드 연산자 | [...arr1, ...arr2] 배열 병합 | 불변 데이터 조작 |
구조 분해 할당 | const {a, b} = obj 값 추출 | 함수형 데이터 변환 |
템플릿 리터럴 | 백틱을 이용한 문자열 보간 | 순수 함수 결과 표현 |
2. 핵심 개념과 구현
2.1 순수 함수 (Pure Functions)
동일한 입력에 항상 동일한 출력을 반환하며 부작용이 없는 함수110:
// 순수 함수
const add = (a, b) => a + b;
const multiply = (x, y) => x * y;
// 비순수 함수 (외부 상태 변경)
let total = 0;
const addToTotal = (amount) => {
total += amount; // 부작용 발생
return total;
};
2.2 불변성 (Immutability)
원본 데이터를 변경하지 않고 새로운 데이터를 생성1112:
// 배열 불변 조작
const numbers = [1, 2, 3, 4];
const doubled = numbers.map(x => x * 2); // [2, 4, 6, 8]
const filtered = numbers.filter(x => x > 2); // [3, 4]
// 객체 불변 갱신
const user = { name: 'John', age: 30 };
const updatedUser = { ...user, age: 31 }; // 새 객체 생성
2.3 고계 함수 (Higher-Order Functions)
// 함수를 인자로 받는 고계 함수
const applyOperation = (arr, operation) => arr.map(operation);
const doubled = applyOperation([1, 2, 3], x => x * 2);
// 함수를 반환하는 고계 함수
const createMultiplier = (factor) => (x) => x * factor;
const triple = createMultiplier(3);
console.log(triple(4)); // 12
3. 자바스크립트 내장 함수형 메서드
3.1 배열 함수형 메서드
자바스크립트는 강력한 배열 함수형 메서드들을 제공한다1314:
메서드 | 기능 | 예시 |
---|---|---|
map() | 각 요소를 변환하여 새 배열 생성 | [^1][^4][^11].map(x => x*2) → [^4][^10][^12] |
filter() | 조건을 만족하는 요소만 선택 | [^1][^4][^11][^10].filter(x => x > 2) → [^11][^10] |
reduce() | 배열을 단일 값으로 축약 | [^1][^4][^11].reduce((a,b) => a+b, 0) → 6 |
find() | 조건을 만족하는 첫 번째 요소 반환 | [^1][^4][^11].find(x => x > 1) → 2 |
some() | 하나라도 조건 만족 시 true | [^1][^4][^11].some(x => x > 2) → true |
every() | 모든 요소가 조건 만족 시 true | [^1][^4][^11].every(x => x > 0) → true |
3.2 메서드 체이닝
여러 함수형 메서드를 연결하여 데이터 파이프라인 구성15:
const products = [
{ name: 'laptop', price: 1000, category: 'electronics' },
{ name: 'book', price: 20, category: 'education' },
{ name: 'phone', price: 500, category: 'electronics' }
];
const expensiveElectronics = products
.filter(p => p.category === 'electronics')
.filter(p => p.price > 600)
.map(p => p.name);
// ['laptop']
4. 고급 함수형 프로그래밍 기법
4.1 클로저 (Closures)
외부 함수의 변수에 접근할 수 있는 내부 함수로, 데이터 캡슐화와 팩토리 패턴에 활용616:
// 클로저를 이용한 프라이빗 변수
const createCounter = () => {
let count = 0;
return {
increment: () => ++count,
decrement: () => --count,
getValue: () => count
};
};
const counter = createCounter();
console.log(counter.increment()); // 1
console.log(counter.getValue()); // 1
4.2 커링 (Currying)
다중 인자 함수를 단일 인자 함수들의 연쇄로 변환1718:
// 일반 함수
const add = (a, b, c) => a + b + c;
// 커링된 함수
const curriedAdd = (a) => (b) => (c) => a + b + c;
const addFive = curriedAdd(5);
const addFiveAndThree = addFive(3);
console.log(addFiveAndThree(2)); // 10
// 부분 적용
const partialAdd = (a, b) => (c) => a + b + c;
const addEight = partialAdd(5, 3);
console.log(addEight(2)); // 10
4.3 함수 합성 (Function Composition)
작은 순수 함수들을 조합하여 복잡한 로직 구성1:
const pipe = (...functions) => (value) =>
functions.reduce((acc, fn) => fn(acc), value);
const compose = (...functions) => (value) =>
functions.reduceRight((acc, fn) => fn(acc), value);
// 파이프라인 예시
const addOne = x => x + 1;
const double = x => x * 2;
const square = x => x * x;
const transform = pipe(addOne, double, square);
console.log(transform(3)); // ((3+1)*2)^2 = 64
4.4 재귀 (Recursion)
// 팩토리얼
const factorial = (n) =>
n <= 1 ? 1 : n * factorial(n - 1);
// 꼬리 재귀 최적화
const factorialTail = (n, acc = 1) =>
n <= 1 ? acc : factorialTail(n - 1, n * acc);
// 배열 평탄화
const flatten = (arr) =>
arr.reduce((acc, val) =>
Array.isArray(val)
? acc.concat(flatten(val))
: acc.concat(val), []);
5. 실전 활용 예제
5.1 데이터 변환 파이프라인
const processUserData = (users) =>
users
.filter(user => user.active)
.map(user => ({
...user,
fullName: `${user.firstName} ${user.lastName}`,
ageGroup: user.age < 30 ? 'young' : 'mature'
}))
.reduce((groups, user) => {
groups[user.ageGroup] = groups[user.ageGroup] || [];
groups[user.ageGroup].push(user);
return groups;
}, {});
5.2 에러 처리를 위한 Maybe 모나드 패턴
const Maybe = {
of: (value) => ({ value, isNothing: value == null }),
map: (maybe, fn) =>
maybe.isNothing ? maybe : Maybe.of(fn(maybe.value)),
chain: (maybe, fn) =>
maybe.isNothing ? maybe : fn(maybe.value)
};
const safeGetUser = (id) =>
Maybe.of(users.find(u => u.id === id));
const getUserEmail = (userId) =>
Maybe.chain(
safeGetUser(userId),
user => Maybe.of(user.email?.toLowerCase())
);
6. 성능 최적화와 주의사항
6.1 성능 고려사항
- 메모이제이션: 비용이 큰 순수 함수의 결과 캐싱
- 지연 평가: 필요할 때까지 계산 지연
- 꼬리 호출 최적화: 재귀 함수의 스택 오버플로우 방지
// 메모이제이션
const memoize = (fn) => {
const cache = new Map();
return (...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 expensiveFunction = memoize((n) => {
console.log(`Computing for ${n}`);
return n * n;
});
6.2 주의사항
- 과도한 중간 배열 생성으로 인한 메모리 사용량 증가
- 깊은 재귀로 인한 스택 오버플로우 위험
- 가독성과 성능 간의 균형 고려
7. 결론
자바스크립트의 함수형 프로그래밍은 코드의 예측 가능성, 테스트 용이성, 모듈성을 크게 향상시킨다213. ES6 이후의 새로운 문법 기능들과 내장 함수형 메서드들을 활용하면, 선언적이고 간결한 코드를 작성할 수 있다. 특히 map
, filter
, reduce
같은 고계 함수와 클로저, 커링 등의 고급 기법을 조합하여 복잡한 데이터 처리 로직을 우아하게 표현할 수 있다.
함수형 프로그래밍은 기존의 명령형 코드를 완전히 대체하는 것이 아니라, 적절한 상황에서 활용하여 코드 품질을 높이는 도구로 활용하는 것이 중요하다. 현대 웹 개발에서 React, Redux 등의 라이브러리들이 함수형 원칙을 적극 도입하고 있어, 함수형 프로그래밍 숙련도는 필수적인 역량이 되고 있다.
Footnotes
-
https://www.toptal.com/javascript/functional-programming-javascript ↩ ↩2 ↩3
-
https://developer.mozilla.org/en-US/docs/Glossary/First-class_Function ↩ ↩2
-
https://www.freecodecamp.org/news/functional-programming-in-javascript/ ↩ ↩2
-
https://www.geeksforgeeks.org/javascript/javascript-higher-order-functions/ ↩ ↩2
-
https://www.freecodecamp.org/news/higher-order-functions-in-javascript-explained/ ↩ ↩2
-
https://hosting.com/blog/what-the-heck-is-a-closure-anyway/ ↩ ↩2
-
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Closures ↩
-
https://dev.to/codingcrafts/javascript-es6-features-every-developer-should-know-12ak ↩
-
https://dev.to/alexmercedcoder/deep-dive-into-functional-programming-in-javascript-851 ↩
-
https://viblo.asia/p/what-is-the-immutability-pattern-in-javascript-aAY4qROKJPw ↩
-
https://dev.to/vishalkinikar/understanding-javascript-immutability-and-reference-types-34pj ↩
-
https://dev.to/dipakahirav/15-javascript-array-functions-you-should-master-as-a-senior-dev-54gg ↩
-
https://www.freecodecamp.org/news/javascript-map-reduce-and-filter-explained-with-examples/ ↩
-
https://scribbler.live/2023/05/23/Closure-in-JavaScript-for-Function-Programming.html ↩
-
https://stackoverflow.com/questions/218025/what-is-the-difference-between-currying-and-partial-application ↩
-
https://www.geeksforgeeks.org/javascript/currying-vs-partial-application-in-javascript/ ↩
-
https://www.sitepoint.com/recursion-functional-javascript/ ↩
-
https://stackoverflow.com/questions/21224345/functional-recursive-method-in-javascript-bad-practise ↩
-
https://hackernoon.com/functional-programming-with-javascript-a-deep-dive ↩