[Functional Programming] 0장 들어가며: 타입스크립트 함수형 프로그래밍

2025. 4. 17. 09:38프로그래밍 공부/javascript & typescript

반응형
SMALL

함수형 프로그래밍을 공부하다 보면, LodashRamda과 더불어 최근에 공개된 es-toolkit 같은 라이브러리를 통해 여러 개념을 체험하게 됩니다.

Functional Programming 시리즈에서 타입스크립트 기반으로 함수형 유틸리티 라이브러리 fx를 직접 설계하고 구현해보고자 합니다.

1. 함수형 프로그래밍이란?

함수형 프로그래밍은 수학적 함수 개념에 기반하여, 상태 변화와 부작용(side effect)을 최소화하고 데이터 흐름을 함수 조합으로 표현하는 프로그래밍 패러다임입니다.
중심 개념은 다음과 같습니다:

  • 불변성 (Immutability): 데이터는 변경되지 않고, 항상 복사본을 리턴
  • 순수 함수 (Pure Function): 같은 입력에 대해 항상 같은 결과를 리턴
  • 고차 함수 (Higher-order Function): 함수를 인자로 받거나 반환
  • 지연 평가 (Lazy Evaluation): 필요할 때만 값을 계산

이러한 원칙을 따르면 코드는 예측 가능하고 테스트하기 쉬워지며, 복잡한 로직을 더 작고 조합 가능한 단위로 나눌 수 있습니다.

2. 자바스크립트에서의 함수형 프로그래밍

자바스크립트는 함수가 일급 객체(first-class citizen)이기 때문에 함수형 프로그래밍을 적용하기에 매우 유연한 언어입니다. 실제로 JS는 map, filter, reduce 같은 배열 메서드를 통해 함수형 스타일을 일부 내장하고 있습니다.

그러나 다음과 같은 한계도 존재합니다

  • 배열(Array)에만 적용 가능 — Set, Map, Generator 등에는 직접적으로 적용 불가능
  • 지연 평가가 기본값이 아님 — map().filter()는 즉시 실행됨
  • 복잡한 체이닝 로직은 중간 변수를 사용해야 가독성을 유지할 수 있음

3. 설계 중점

이 프로젝트의 중심은 단순합니다.

"이터러블(iterable)을 기반으로 한 체이너블 함수형 유틸리티를 만든다."

설계 원칙은 다음과 같았습니다:

  • 이터러블 기반: Array 뿐만 아니라 Set, Map, Generator 등 모든 iterable에 동작
  • 지연 평가: range(), filter(), map() 등이 실제로 쓰일 때까지 계산하지 않음
  • 타입 안전: 타입스크립트 기반으로, 타입 추론이 가능한 설계
  • 체이닝 중심: Fx.of(data).map(...).filter(...).take(...) 형태의 파이프라인 지원
  • 불변성 유지: 연산 후 원본 변경 없음. 항상 새로운 Fx 인스턴스 반환

4. 로직이 아니라 흐름

이 프로젝트에서 가장 중요하게 생각한 점은 로직보다 흐름에 집중하자는 것이었습니다.

기존 코드: 메모리 폭발

  • 100만 개를 먼저 전부 생성
  • 그 중 일부만 사용 ➝ 불필요한 계산 낭비
const topSquares = Array.from({ length: 1_000_000 }, (_, i) => i)
  .map(x => x * x)
  .filter(x => x % 2 === 0)
  .slice(0, 10);

지연 평가 방식으로 처리하게 되면 아래와 같이 바꿀 수 있습니다

const topSquares = Fx.of(range(1_000_000))
  .map(x => x * x)            // 무한히 제곱을 생성
  .filter(x => x % 2 === 0)   // 짝수 제곱만 통과
  .take(10)                   // 상위 10개만 평가
  .toArray();

console.log(topSquares);
// 출력: [0, 4, 16, 36, 64, 100, 144, 196, 256, 324]

range함수는 yield 키워드를 이용하여 아래와 같이 구현할 수 있습니다.

export function range(end: number): Generator<number>;
export function range(start: number, end: number): Generator<number>;
export function range(start: number, end: number, step: number): Generator<number>;

export function* range(start: number, end?: number, step: number = 1): Generator<number> {
  if (end === undefined) {
    end = start;
    start = 0;
  }
  if (step < 0) {
    for (let i = start; i > end; i = i + step) yield i;
  }
  for (let i = start; i < end; i = i + step) yield i;
}
반응형
LIST