💻 프로그래밍

Functional Programming

함수형 프로그래밍

순수 함수와 불변성 강조하는 패러다임. 부수효과 최소화.

📖 상세 설명

함수형 프로그래밍(Functional Programming)은 수학적 함수의 개념에 기반한 프로그래밍 패러다임으로, 순수 함수(Pure Function)와 불변 데이터를 핵심 원칙으로 삼습니다. 1950년대 John McCarthy가 개발한 Lisp에서 시작되어, 오늘날 Haskell, Scala, Clojure 등의 언어와 JavaScript, Python의 함수형 기능으로 널리 활용되고 있습니다.

순수 함수는 동일한 입력에 대해 항상 동일한 출력을 반환하고, 외부 상태를 변경하지 않는 함수입니다. 이러한 참조 투명성(Referential Transparency) 덕분에 코드의 예측 가능성이 높아지고, 테스트와 디버깅이 쉬워지며, 병렬 처리에서 안전하게 동작합니다.

함수형 프로그래밍의 핵심 개념으로는 일급 함수(First-class Function), 고차 함수(Higher-order Function), 클로저(Closure), 커링(Currying), 합성(Composition) 등이 있습니다. map, filter, reduce 같은 고차 함수를 활용하면 반복문 없이도 데이터 변환을 선언적으로 표현할 수 있어 코드가 간결하고 의도가 명확해집니다.

실무에서 함수형 프로그래밍은 React의 상태 관리(Redux), RxJS를 활용한 반응형 프로그래밍, 데이터 파이프라인 처리 등에서 광범위하게 적용됩니다. 특히 AI/ML 분야에서 데이터 전처리와 변환 파이프라인 구축에 함수형 패턴이 필수적으로 사용되며, 부작용 없는 코드는 마이크로서비스와 분산 시스템에서 안정성을 크게 높여줍니다.

💻 코드 예제

// 1. 순수 함수 vs 비순수 함수
// ❌ 비순수 함수: 외부 상태 의존 및 변경
let discount = 0.1;
function getPrice(price) {
    return price * (1 - discount); // 외부 변수 의존
}

// ✅ 순수 함수: 입력만으로 결과 결정
const getPurePrice = (price, discountRate) => price * (1 - discountRate);

// 2. 불변성 유지 (Immutability)
const user = { name: '김철수', age: 30 };

// ❌ 객체 직접 수정
// user.age = 31;

// ✅ 새 객체 생성 (스프레드 연산자)
const updatedUser = { ...user, age: 31 };
console.log(user);        // { name: '김철수', age: 30 }
console.log(updatedUser); // { name: '김철수', age: 31 }

// 3. 고차 함수 활용 (map, filter, reduce)
const products = [
    { name: '노트북', price: 1200000, category: '전자' },
    { name: '책상', price: 300000, category: '가구' },
    { name: '모니터', price: 400000, category: '전자' },
    { name: '의자', price: 250000, category: '가구' }
];

// 전자 제품만 필터링 후 10% 할인 적용
const discountedElectronics = products
    .filter(p => p.category === '전자')
    .map(p => ({
        ...p,
        price: p.price * 0.9,
        discounted: true
    }));

// 총 가격 계산
const totalPrice = discountedElectronics
    .reduce((sum, p) => sum + p.price, 0);

console.log(`할인된 전자제품 총액: ${totalPrice.toLocaleString()}원`);

// 4. 함수 합성 (Function Composition)
const compose = (...fns) => x => fns.reduceRight((v, f) => f(v), x);
const pipe = (...fns) => x => fns.reduce((v, f) => f(v), x);

const trim = str => str.trim();
const toLowerCase = str => str.toLowerCase();
const replaceSpaces = str => str.replace(/\s+/g, '-');

// 슬러그 생성 파이프라인
const createSlug = pipe(trim, toLowerCase, replaceSpaces);
console.log(createSlug('  Hello World  ')); // 'hello-world'

// 5. 커링 (Currying)
const multiply = a => b => a * b;
const double = multiply(2);
const triple = multiply(3);

console.log(double(5));  // 10
console.log(triple(5));  // 15

// 실용적 커링 예제: API 요청 빌더
const createRequest = baseUrl => endpoint => params =>
    fetch(`${baseUrl}${endpoint}?${new URLSearchParams(params)}`);

const apiRequest = createRequest('https://api.example.com');
const usersApi = apiRequest('/users');
// usersApi({ page: 1, limit: 10 });

🗣️ 실무에서 이렇게 말하세요

💬 회의에서
"이 데이터 파이프라인은 함수형으로 구현하면 좋겠습니다. map과 filter 체이닝으로 각 변환 단계를 명확하게 표현하고, 순수 함수로 작성하면 단위 테스트도 쉬워지고 나중에 병렬 처리로 전환하기도 수월합니다."
💬 면접에서
"함수형 프로그래밍의 핵심은 순수 함수와 불변성입니다. 순수 함수는 같은 입력에 항상 같은 출력을 반환하고 부작용이 없어서, 테스트가 쉽고 동시성 프로그래밍에서 race condition을 방지할 수 있습니다. Redux가 불변성을 강조하는 것도 이런 이점 때문입니다."
💬 코드 리뷰에서
"이 for 루프를 reduce로 바꾸면 어떨까요? 현재 코드는 외부 변수를 mutate하고 있어서 부작용이 있는데, reduce를 쓰면 불변성을 유지하면서 같은 결과를 더 선언적으로 얻을 수 있습니다."

⚠️ 흔한 실수 & 주의사항

배열/객체 직접 수정하기

push, pop, splice, 객체 속성 직접 할당은 원본을 변경합니다. spread 연산자, concat, filter, map 등 새 배열/객체를 반환하는 메서드를 사용하세요. Immer 라이브러리도 불변성 관리에 도움됩니다.

함수 안에서 외부 변수 참조/수정

전역 변수나 클로저로 캡처된 외부 상태에 의존하면 순수 함수가 아닙니다. 필요한 모든 값은 인자로 받고, 결과만 반환하세요. 부작용이 필요하면 명시적으로 분리하세요.

과도한 체이닝으로 성능 저하

map().filter().map() 같은 체이닝은 가독성은 좋지만, 대용량 배열에서는 여러 번 순회합니다. transducer 패턴이나 reduce로 단일 패스 처리를 고려하세요.

점진적으로 함수형 도입하기

모든 코드를 한번에 함수형으로 바꿀 필요 없습니다. 데이터 변환 로직부터 시작해서 순수 함수로 분리하고, 부작용은 경계에서 관리하는 방식으로 점진적으로 적용하세요.

🔗 관련 용어

📚 더 배우기