Back to writing

멀티패러다임이 현대 언어를 확장하는 방법

Table of contents

1.1 객체지향 디자인 패턴의 반복자 패던과 일급 함수

  • 요즘의 멀티 패러다임 언어에서는 함수형 패러다임을 적용하는 방법으로 반복자(iterator) 패턴을 활용함
  • 반복자 패턴은 GoF의 객체지향 디자인 패턴 중 하나였음

1.1.1 GoF의 반복자 패턴

  • 반복자 패턴은 객체지향 디자인 패턴중에 하나로, 컬렉션의 요소를 순차적으로 접근하는 규약을 제시
  • 반복자 패턴은 컬렉션의 내부 구조를 노출하는 대신 next 같은 public 메서드로 내부 요소에 접근할 수 있도록 설계됨

1.1.2 지연 평가되는 map 함수

  • map은 Iterator<A>와 A를 B로 변환하는 transform 함수를 받아 지연된 Iterator<B>를 반환하는 함수
  • 일급 함수: 함수를 값처럼 다루어 변수에 담거나 다른 함수의 인자로 전달하거나 함수의 반환값으로 사용할 수 있는 함수를 일급 함수라고 함
  • 고차 함수: 하나 이상의 함수를 인자로 받거나 반환하는 함수를 말함
  • 반복자 패턴의 지연성은 지연 평가가 가능한 객체를 생성할 수 있게 해주고 일급 함수는 고차함수를 정의할 수 있게함

1.2 명령형 프로그래밍으로 이터레이터를 만드는 제너레이터 함수

  • 제너레이터는 반복자 패턴인 이터레이터를 명령형 코드로 구현하고 생성할 수 있는 도구
  • 어떤 문제는 명령형 스타일로 해결하는 것이 더 효율적이면서 직관적임

1.2.1 제너레이터 기본 문법

  • 제너레이터는 명령형 스타일로 이터레이터를 작성할 수 있게 해주는 문법
  • 곧바로 실행되지 않고 이터레이터 객체를 반환함
  • 이 객체를 통해 함수의 실행 흐름을 외부에서 제어할 수 있음

yield* 키워드는 제너레이터 함수 안에서 이터러블을 순회하며 해당 이터러블이 제공하는 요소들을 순차적으로 반환하도록 해줌

1.3 자바스크립트에서 반복자 패턴 사례: 이터레이션 프로토콜

  • 이터레이션 프로토콜은 자바스크립트의 규약임
  • 어떤 객체가 이터러블인지 여부를 나타내는 규칙과 해당 규칙을 따르는 문법들을 제공하는 언어 전반의 규약

1.3.1 이터레이터와 이터러블

  • 어떤 객체가 이터레이터를 반환하는 [Symbol.iterator]() { return { next() {...} } } 메서드를 가지고 있다면 이터러블

  • 이터러블 객체는 for..of문, 전개 연산자, 구조 분해 등 다양한 기능과 함께 사용할 수 있음

  • 대표적으로 Array, Map, Set 등이 있고 Web API도 컬렉션 유형의 값들을 이터러블로 만들어 이터레이션 프로토콜을 따르고 있음

  • Iterator : { value, done } 객체를 반환하는 next() 메서드를 가진 값

  • Iterable : 이터레이터를 반환하는 [Symbol.Iterator()] 메서드를 가진 값

  • IterableIterator: 이터레이터면서 이터러블인 값

  • 이터레이션 프로토콜: 이터러블을 for..of문, 전개 연산자 등과 함께 동작하도록 한 규약

1.4 이터러블을 다루는 함수형 프로그래밍

1.4.1 forEach

function forEach<T>(f: (a: T) => void, iterable: Iterable<T>) {
  for (const a of iterable) {
    f(a);
  }
}

forEach(console.log, naturals(3));

map

function map<T, U>(f: (a: T) => U, iterable: Iterable<T>): IterableIterator<U> {
  function* iterator() {
    for (const a of iterable) {
      yield f(a);
    }
  }
  return iterator();
}

forEach(
  console.log,
  map(x => x * x, naturals(3))
);

filter

function filter<T>(
  predicate: (a: T) => boolean,
  iterable: Iterable<T>
): IterableIterator<T> {
  function* iterator() {
    for (const a of iterable) {
      if (predicate(a)) yield a;
    }
  }
  return iterator();
}

forEach(
  console.log,
  filter(x => x % 2 === 0, naturals(10))
);

1.5 이터러블 프로토콜이 상속이 아닌 인터페이스로 설계된 이유

  • 반복자 패턴과 이터레이터를 지원하는 헬퍼 함수들은 상속이 아닌 인터페이스로 설계되어 있음
  • Arraymap, filter를 사용하지 않고 이터러블을 사용하는 이유는 이터레이션 프로토콜은 ‘외부 구조의 다형성’을 해결하기 때문임
  • 인터페이스는 규약을 제시하여 다양한 클래스가 동일한 형식의 동작을 구현하도록 유도함
  • 반면 상속은 공통 기능을 직접 구현한 뒤 이를 적절히 확장하는 데 초점을 둠
  • 인터페이스는 언어나 표준 라이브러리 설계 단계에서 자주 사용되고 상속은 주로 SDK나 애플리케이션 레벨에서 사용됨