6장 함수형 프로그래밍

정수를 제곱하기

25까지 정수 제곱을 출력하는 예제를 자바(Java)와 클로져(Clojure)는 각각 다음과 같이 구현할 수 있다.

// Java
public class Squint {
  public static void main(String args[]) {
    for (int i=0; i<25; i++)
      System.out.println(i*i);
  }
}
;; Clojoure
(println (take 25 (map (fn [x] (* x x)) (range))))

자바는 i라는 가변 변수(mutable variable) 를 사용했지만 클로저에는 가변 변수가 없다. 즉 함수형 언어에서 변수는 변경되지 않는다.

불변성과 아키텍처

아키텍처에서 변수의 가변성이 왜 중요한가? 답은 경합 조건(race condition), 교착상태(deadlock), 동시 업데이트(concurrent update) 문제가 모두 가변 변수로 인해 발생하기 때문이다. 다수의 스레드와 프로세스를 사용하는 애플리케이션에서 마주치는 모든 문제는 가변 변수가 없다면 절대로 생기지 않는다.

저장 공간이 무한하고 프로세서의 속도가 무한히 빠르면 불변성이 실현 가능하겠지만 자원이 무한대가 아니기 때문에 타협이 필요하다.

가변성의 분리

가장 주요한 타협 중 하나는 애플리케이션 내부를 가변 컴포넌트와 불변 컴포넌트로 분리하는 일이다. 불변 컴포넌트에선 함수형 방식으로 작업을 처리하고 가변 변수를 사용하지 않는다. 불변 컴포넌트는 1개 이상의 가변 컴포넌트와 통신한다.

가변 컴포넌트는 여러 동시성 문제에 노출되기 때문에 트랜잭션 메모리(transactional memory) 와 같은 방법을 사용해 가변 변수를 보호한다. 트랜잭션 메모리는 데이터베이스가 디스크의 레코드를 다루는 방식과 동일한 방식으로 메모리의 변수를 처리한다. 이러한 트랜잭션 방법 또는 재시도 기법을 통해 변수를 보호한다.

현명한 아키텍트라면 가능 한 많은 처리를 불변 컴포넌트에서 해야한다.

이벤트 소싱

최근 컴퓨팅 자원이 강력해지면서 우리에게 필요한 가변 상태는 적어졌다. 계좌 잔고를 관리하는 은행 애플리케이션이 있는데, 계좌의 잔고를 직접 변경하는 대신 불변 상태를 유지하기 위해 트랜잭션 자체를 저장할 수도 있다. 이벤트 소싱은 이렇게 상태가 아닌 트랜잭션을 저장하는 전략이다. 상태가 필요해지면 모든 트랜잭션을 처리한다.

이 전략은 저장 공간이 많이 필요하지만 최근엔 저장 공간을 충분히 확보할 수 있다. 또한 애플리케이션은 CRUD가 아니라 CR만 수행하고, 데이터 저장소에서도 변경과 삭제가 발생하지 않기 때문에 동시 업데이트 문제가 일어나지 않는다.

따라서 저장 공간과 처리 능력이 충분하면 애플리케이션이 완전한 불변성을 갖도록 만들 수 있고, 완전한 함수형으로 만들 수 있다.

결론

  • 구조적 프로그래밍은 제어흐름의 직접적인 전환에 부과되는 규율이다

  • 객체 지향 프로그래밍은 제어흐름의 간접적인 전환에 부과되는 규율이다

  • 함수형 프로그래밍은 변수 할당에 부과되는 규율이다

  • 각 패러다임은 코드를 작성하는 방식을 제한했다.

  • 도구가 달라지고 하드웨어도 변했지만 소프트웨어의 핵심은 튜링이 코드를 작성할 때와 다르지 않다.

Last updated