[클린 아키텍처] 6. 함수형 프로그래밍

햄도·2021년 7월 22일
0

Clean Architecture

목록 보기
6/11

출처

클린 아키텍처를 읽으며 정리한 내용입니다.

6. 함수형 프로그래밍

함수형 프로그래밍이라는 개념은 람다 계산법(알론조 처치, 1930)이라는 형태로 프로그래밍보다 앞서 등장했다.

정수를 제곱하기

정수를 제곱하는 프로그램을 자바에서는 아래와 같이 작성할 수 있다.

public class Squint {
    public static void main(String args[]) {
        for (int i=0; i<25; i++)
            System.out.prinln(i*i);
	}
}

리스프에서 파생한 클로저는 함수형 언어로, 다음과 같이 구현할 수 있다.

(println ;___ 출력한다
    (take 25 ;___ 처음부터 25까지
        (map (fn [x] (*x x)) ;___ 제곱을
            (range)))) ;___ 정수의
  • println, take, map, range는 모두 함수이며, 리스프에서는 함수를 괄호 안에 넣는 방식으로 호출한다.
  • (fn [x] (* x x))는 익명 함수로, 곱셈 함수를 호출하면서 입력 인자를 두 번 전달하여 입력의 제곱을 계산한다.
  • 가장 안쪽의 호출부터 시작해 보는 게 좋다.
    • range: 0부터 시작하는 끝이 없는 정수 리스트 반환
    • map: 반환된 리스트를 받아 각 정수에 대해 제곱을 계산, 모든 정수의 제곱이 들은 끝없는 리스트 반환
    • take: 제곱된 리스트를 받아 앞에서 25개까지만 새로운 리스트로 반환
    • println: take가 반환한 값 출력

예시에서 보았듯이, 자바 프로그램은 프로그램 실행 중 상태가 변할 수 있는 가변 변수를 사용하는 반면 클로저에서는 가변 변수가 없다. 함수형 언어에서는 x와 같은 변수가 한 번 초기화되면 변하지 않는다.

불변성과 아키텍처

왜 변수의 가변성이 아키텍처에서 중요할까?

경합 조건, 교착상태, 동시 업데이트 등의 문제는 모두 가변 변수로 인해 발생한다.

어떤 변수도 갱신되지 않는다면 동시성 애플리케이션에서 마주치는 경합 조건이나 동시 업데이트 문제가 일어나지 않는다.

이런 불변성은 자원이 무한대라면 실현 가능하다. 하지만 무한대가 아니라면.. 타협이 필요하다.

가변성의 분리

가장 중요한 타협 중 하나는 애플리케이션, 또는 애플리케이션의 내부 서비스를 가변 컴포넌트와 불변 컴포넌트로 분리하는 일이다.

불변 컴포넌트?

  • 함수형 방식으로만 작업이 처리되며, 가변변수가 사용되지 않는다.
  • 변수의 상태를 변경할 수 있는 하나 이상의 다른 컴포넌트와 서로 통신한다.
  • 트랜잭션 메모리가 DB와 같이 트랜잭션을 사용하거나 재시도 기법을 통해 이 변수를 보호한다.

이러한 접근법의 예시로 클로저의 atom 기능을 들 수 있다.

(def counter (atom 0)) ; ___ counter를 0으로 초기화
(swap! counter inc); ___ counter를 안전하게 증가

예시에서 counter 변수는 atom으로 정의되었고, atom의 값을 변경하려면 반드시 swap! 함수를 사용해야 한다.

swap! 함수는 변경할 atom 변수와, atom의 새로운 값을 계산할 함수를 받아 전통적인 스왑 알고리즘을 실행한다.

  1. counter의 값을 읽은 후 inc로 전달

  2. inc 함수가 반환하면 counter의 값을 잠그고 inc 함수로 전달했던 값과 비교

    • 값이 같음 → inc 함수가 반환한 값이 counter에 저장되고 잠금 해제
    • 값이 다름 → 잠금을 해제한 후 재시도

애플리케이션을 제대로 구조화하려면 변수를 변경하는 컴포넌트와 변경하지 않는 컴포넌트를 분리하며, 가능한 많은 처리를 불변 컴포넌트로 옮겨야 한다.

이를 위해 가변 변수를 보호하는 적절한 수단을 동원해야 한다.

이벤트 소싱

더 많은 메모리를 확보할수록, 기계가 더 빨라질수록, 필요한 가변 상태는 더 적어진다.

예시: 고객의 계좌 잔고를 관리하는 은행 어플리케이션

  • 잔고 대신 트랜잭션을 저장하고, 잔고 조회 요청이 올때마다 계좌 개설 시점부터 발생한 모든 트랜잭션을 더한다면? 가변 변수 하나 없이 잔고 계산 가능!
  • 영원히 처리하려면 무한한 자원이 필요하겠지만, 애플리케이션의 수명주기 동안만 문제없이 동작할 정도면 된다.

이벤트 소싱은 상태가 아닌 트랜잭션을 저장하는 전략으로, 상태가 필요해지면 상태의 시작점부터 모든 트랜잭션을 저장한다.

이렇게 하면 저장소에서 삭제되거나 변경되는 것은 하나도 없다. 변경과 삭제가 일어나지 않으므로 동시 업데이트 문제도 일어나지 않는다.

이처럼 저장 공간과 처리 능력이 충분하면 애플리케이션이 완전한 불변성을 갖도록 만들 수 있고, 완전한 함수형으로 만들 수 있다. 소스 코드 버전 관리 시스템도 이런 식으로 동작한다!

결론

세 프로그래밍 패러다임은 다음과 같은 것들을 앗아가며 코드를 작성하는 방식을 한정시켰다.

  • 구조적 프로그래밍: 제어흐름의 직접적인 전환에 부과되는 규율 → goto를 앗아감
  • 객체 지향 프로그래밍: 제어흐름의 간접적인 전환에 부과되는 규율 → 함수포인터를 앗아감
  • 함수형 프로그래밍: 변수 할당에 부과되는 규율 → 할당문을 앗아감

시간이 많이 흘렀음에도 지금의 소프트웨어 규칙은 최초의 코드를 작성할 때와 조금도 달라지지 않았으며, 컴퓨터 프로그램은 순차, 분기, 반복, 참조 네 개의 조합이다.

profile
developer hamdoe

0개의 댓글