[Functional Programming] 2장: iterator 함수 range 구현하기

2025. 4. 21. 08:00프로그래밍 공부/javascript & typescript

반응형
SMALL

함수형 유틸리티를 만들 때 빠질 수 없는 기본 도구 중 하나는 바로 range입니다.
range는 원하는 숫자 범위를 순차적으로 생성해주는 반복기(iterator)이며, 지연 평가와 매우 잘 어울리는 함수입니다.

이 장에서는 TypeScript에서 제너레이터와 함수 오버로드를 이용해 range 함수를 구현하고, 이를 테스트하는 과정을 설명합니다.

range 함수란?

range는 Python을 비롯한 많은 함수형 언어에서 기본 제공되는 기능입니다.

  • range(5) → [0, 1, 2, 3, 4]
  • range(2, 5) → [2, 3, 4]
  • range(0, 10, 2) → [0, 2, 4, 6, 8]
  • range(5, 0, -1) → [5, 4, 3, 2, 1]

이러한 형태를 TypeScript에서 구현하면서도 이터러블을 지연 평가하는 구조를 채택함으로써 메모리 낭비를 줄이고 다양한 함수형 유틸리티와 자연스럽게 연결될 수 있도록 구성했습니다.

구현 코드

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 += step) yield i;
  } else {
    for (let i = start; i < end; i += step) yield i;
  }
}

제너레이터

range는 한 번에 모든 값을 생성하는 것이 아니라, 필요할 때 하나씩 생성하는 함수입니다.
즉, lazy evaluation (지연 평가)를 통해 메모리 낭비 없이 사용할 수 있어야 하며, 이는 function*으로 정의된 제너레이터 함수가 가장 잘 어울립니다.

예를 들어, 다음 코드는 range(1_000_000)을 써도 메모리 사용량이 매우 낮습니다.

for (const num of range(1_000_000)) {
  if (num > 5) break;
  console.log(num); // 0, 1, 2, 3, 4, 5
}

overloads

TypeScript에서는 다양한 인자 조합에 따라 함수 시그니처를 다르게 정의할 수 있도록 overloads를 지원합니다.

range()의 입력 형태에 따라 타입 추론이 정확하게 작동합니다.

range(5);        // start: 0, end: 5
range(2, 5);     // start: 2, end: 5
range(0, 10, 2); // start: 0, end: 10, step: 2

사용 예시

Fx.of(range(1, 100))
  .filter(x => x % 2 === 0)
  .map(x => x * x)
  .take(5)
  .toArray(); // [4, 16, 36, 64, 100]
반응형
LIST