함수형 프로그래밍의 핵심: 순수 함수, 커링, 함수 조합

순수 함수의 개념과 특성

함수형 프로그래밍에서 중요한 원칙 중 하나는 순수 함수이다. 이는 입력 값이 동일할 경우 항상 동일한 출력을 반환하며, 외부 상태나 입출력 장치에 의존하지 않으며, 부작용이 발생하지 않는 함수를 의미한다.

  • 입력이 같으면 출력도 반드시 같다
  • 함수 내부에서 전역 변수 변경, 매개변수 수정, 외부 상태 변경 등의 부작용이 없어야 한다

부작용 예시와 비교

예를 들어, 배열의 slice()는 원본 데이터를 변경하지 않고 새로운 배열을 반환하므로 순수 함수이다. 반면, splice()는 원본 배열을 직접 수정하므로 부작용을 유발하여 순수 함수가 아니다.

const items = ['A', 'B', 'C', 'D'];

// 순수 함수: 결과는 예측 가능하고 원본 유지
const result1 = items.slice(0, 2);
console.log(result1); // ['A', 'B']
console.log(items);   // ['A', 'B', 'C', 'D'] (원본 그대로)

// 비순수 함수: 원본 변경
const result2 = items.splice(0, 2);
console.log(result2); // ['A', 'B']
console.log(items);   // ['C', 'D'] (변경됨)

순수 함수의 활용 사례

다음은 순수 함수의 예제들이다.

// 순수 함수: 인자만으로 계산, 외부 영향 없음
function calculate(a, b) {
  return a * 2 + b * b;
}

// 비순수 함수: 전역 변수 변경
let state = 'initial';
function updateState() {
  console.log('Side effect');
  state = 'updated';
}

// 비순수 함수: 매개변수 객체를 수정
function modifyUser(user) {
  user.age = 25;
}

const user = { age: 20 };
modifyUser(user);

// 순수 함수: 새로운 객체 반환
function createUser(data) {
  return { ...data, age: 30 };
}

커링(Currying): 다중 인자를 단일 인자로 분리

커링은 여러 인자를 받는 함수를, 하나씩 인자를 받아들이며 점진적으로 실행되는 구조로 변환하는 기법이다. 이는 함수의 책임을 명확히 하고 재사용성을 높인다.

기본 구현 예제

// 일반 함수
function add(x, y, z) {
  return x + y + z;
}

// 커링된 버전
const curriedAdd = x => y => z => x + y + z;

console.log(curriedAdd(1)(2)(3)); // 6

실제 활용: 로깅 시스템 개선

예를 들어, 일정 시간대에 특정 타입의 메시지를 로그하는 경우, 커링을 통해 고정된 날짜와 타입을 미리 설정하고 나머지 메시지만 전달하면 된다.

// 커링 적용 전
function log(date, type, msg) {
  console.log(`[${date.getHours()}:${date.getMinutes()}] [${type}] ${msg}`);
}

log(new Date(), 'INFO', '서버 시작');

// 커링 적용 후
const logger = date => type => msg => {
  console.log(`[${date.getHours()}:${date.getMinutes()}] [${type}] ${msg}`);
};

const nowInfoLog = logger(new Date())('INFO');
nowInfoLog('서버 시작');
nowInfoLog('데이터베이스 연결 완료');

커링 함수 생성기 구현

어떤 함수든 커링 형태로 변환할 수 있는 유틸리티 함수를 작성할 수 있다.

function curry(fn) {
  return function curried(...args) {
    if (args.length >= fn.length) {
      return fn.apply(this, args);
    } else {
      return function(...nextArgs) {
        return curried.apply(this, args.concat(nextArgs));
      };
    }
  };
}

const curriedCalc = curry((a, b, c) => a * 2 + b * 3 + c * 4);
console.log(curriedCalc(1)(2)(3)); // 20

함수 조합(Compose): 함수의 체인적 실행

여러 함수를 결합하여 하나의 새로운 함수로 만들 수 있으며, 이를 통해 코드의 가독성과 재사용성을 향상시킬 수 있다. 조합은 오른쪽에서 왼쪽으로 실행된다.

간단한 조합 예제

function double(n) { return n * 2; }
function square(n) { return n * n; }

// 수동 조합
const composed = n => square(double(n));
console.log(composed(5)); // 100

// 일반화된 조합 함수
function compose(...funcs) {
  return function(value) {
    return funcs.reduceRight((acc, fn) => fn(acc), value);
  };
}

const pipeline = compose(double, square);
console.log(pipeline(5)); // 100

고급 조합: 함수 체인 처리

여러 함수를 순차적으로 적용하는 경우, 다음과 같이 확장 가능한 구조를 설계할 수 있다.

function pipe(...fns) {
  return function(value) {
    return fns.reduce((acc, fn) => fn(acc), value);
  };
}

const process = pipe(
  double,
  square,
  n => n + 10
);

console.log(process(3)); // ((3*2)^2)+10 = 46

태그: JavaScript 함수형 프로그래밍 순수 함수 커링 함수 조합

7월 20일 02:28에 게시됨