들어가며
왜 함수형 프로그래밍을 배워야 할까?
프론트엔드 개발을 하다보면, React, Redux 같은 도구들을 자주 만나게 됩니다.
이들은 모두 함수형 프로그래밍 (Functional Programming, 이하 FP) 패러다임을 기반으로 설계되어 있습니다.
또한, 비동기 처리가 복잡해지면서, 상태 관리의 어려움은 개발자들의 골칫거리가 되고 있죠.
함수형 프로그래밍은 이런 문제들을 다르게 접근하는 방식입니다. 상태 변화를 최소화하고, 함수를 조합하는 방식으로 코드를 작성하면, 예측 가능하고 테스트하기 쉬운 코드를 만들 수 있습니다.
이 글에서는 함수형 프로그래밍의 핵심 개념들을 하나씩 살펴보겠습니다.
내용
1.순수 함수 (Pure Function)
함수형 프로그래밍의 기초는 순수 함수입니다. 순수 함수란 다음 두 가지를 만족하는 함수 입니다.
- 동일한 입력에 항상 동일한 출력을 반환한다. (결정적)
- 함수 외부의 상태를 변경하지 않는다 (부작용 없음)
//순수하지 않은 함수
let counter = 0;
function incrementCounter(num){
counter += num; //외부 상태를 변경
return counter;
}
//같은 입력인데 다른 출력
console.log(incrementCounter(5)); //5
console.log(incrementCounter(5)); //10//순수한 함수
function add(a, b) {
return a + b;
}
// 같은 입력, 항상 같은 출력
console.log(add(5, 3)); // 8
console.log(add(5, 3)); // 8왜 순수 함수가 중요할까요? 순수 함수는 테스트 하기 쉽고, 동작을 예측하기 쉽고, 동시에 실행해도 안전합니다.
실무에서 버그를 줄이려면 가능한 한 많은 부분을 순수 함수로 만들어야 합니다.
2.불변성 (Immutability)
함수형 프로그래밍에서는 한 번 만들어진 데이터를 변경하지 않습니다. 대신 새로운 데이터를 만듭니다.
// 가변적인 접근
const user = { name: '김철수', age: 30 };
function celebrateBirthday(person){
person.age += 1; // 원본 객체를 직접 변경
return person;
}
console.log(celebrateBirthday(user)); // { name: '김철수', age: 31 }
console.log(user); // { name: '김철수', age: 31 } - 원본도 변경됨// 불변적인 접근
const user = { name: '김철수', age: 30 };
function celebrateBirthday(person) {
return { ...person, age: person.age + 1 }; // 새로운 객체 생성
}
const updatedUser = celebrateBirthday(user);
console.log(updatedUser); // { name: '김철수', age: 31 }
console.log(user); // { name: '김철수', age: 30 } - 원본 유지불변성의 장점
- 예기치 않은 부작용으로 인한 버그가 줄어듭니다.
- 데이터 흐름을 추적하기 쉬워집니다.
- 이전 상태로 돌아가기 쉽습니다. (시간 여행 디버깅)
3.일급 함수 (First-class Function)
함수형 프로그래밍에서는 함수를 값처럼 다룹니다. 즉, 함수를 변수에 저장하고, 함수의 인자로 전달하고, 함수를 반환할 수 있습니다.
// 함수를 변수에 저장
const greet = (name) => `안녕, ${name}!`;
// 함수를 다른 함수의 인자로 전달
function executeFunction(fn, value) {
return fn(value);
}
console.log(executeFunction(greet, '철수')); // "안녕, 철수!"
// 함수를 반환하는 함수
function makeMultiplier(factor) {
return (number) => number * factor;
}
const double = makeMultiplier(2);
console.log(double(5)); // 104.고차함수 (Higher-Order Function)
고차 함수는 함수를 인자로 받고나, 함수를 반환하는 함수입니다.
// 함수를 인자로 받는 고차 함수
const numbers = [1, 2, 3, 4, 5];
function map(array, transformFn) {
const result = [];
for (const item of array) {
result.push(transformFn(item));
}
return result;
}
const doubled = map(numbers, (n) => n * 2);
console.log(doubled); // [2, 4, 6, 8, 10]
// 함수를 반환하는 고차 함수
function createValidator(pattern) {
return (text) => pattern.test(text);
}
const isEmail = createValidator(/@/);
console.log(isEmail('test@example.com')); // true
console.log(isEmail('test')); // false5.함수 조합 (Function Composition)
여러 함수를 조합하여 새로운 함수를 만드는 것을 함수 조합이라 합니다. 이것이 함수형 프로그래밍의 핵심입니다.
// 간단한 함수들을 정의
const add10 = (n) => n + 10;
const multiply2 = (n) => n * 2;
const subtract5 = (n) => n - 5;
// 함수 조합 유틸리티
function compose(...fns) {
return (value) => fns.reduceRight((acc, fn) => fn(acc), value);
}
// 여러 함수를 조합
const calculate = compose(subtract5, multiply2, add10);
// (5 + 10) * 2 - 5 = 25
console.log(calculate(5)); // 25이렇게 작은 순수 함수들을 조합하면 복잡한 로직도 이해하기 쉽고 테스트하기 쉬워집니다.
6.실무 예시: Redux에서의 함수형 프로그래밍
Redux는 함수형 프로그래밍 원칙을 철저히 따릅니다. Reducer는 순수 함수여야 합니다.
// 순수하지 않은 reducer
function counterReducer(state = 0, action) {
if (action.type === 'INCREMENT') {
state += 1; // 상태를 직접 변경 - 위험!
}
return state;
}
// 순수한 reducer
function counterReducer(state = 0, action) {
if (action.type === 'INCREMENT') {
return state + 1; // 새로운 값 반환
}
return state;
}
// 더 실무적인 예시
function userReducer(state = {}, action) {
switch (action.type) {
case 'SET_USER':
return { ...state, ...action.payload }; // 불변성 유지
case 'CLEAR_USER':
return {};
default:
return state;
}
}마무리
함수형 프로그래밍은 패러다임의 전환입니다. 객체 지향 프로그래밍이 “객체들이 상태를 가지고 메시지를 주고받는다” 고 생각한다면, 함수형 프로그래밍은 “순수 함수들을 조합하여 데이터를 변환한다” 고 생각합니다.
핵심 정리:
- 순수 함수: 예측 가능하고 테스트하기 쉬운 코드의 기초
- 불변성: 버그를 줄이고, 데이터 흐름을 명확히 함
- 일급 함수: 함수를 값처럼 다루어 유연한 조합 가능
- 함수 조합: 작은 함수들로 복잡한 로직을 구성