Post
EN

함수형 프로그래밍

함수형 프로그래밍이라는 개념은 프로그래밍 그 자체보다 앞서 등장했다. 이 패러다임에서 핵심이 되는 기반은 람다(lamda) 계산법으로 알론조 처치(Alonzo Church)가 1930년대에 발명했다.

함수형 프로그래밍이 무엇인지 설명하려면 아래 예제 코드를 보고 확인해보자.

정수를 제곱하는 자바로 작성된 함수가 있다

![]()

리스프에서 파생된 클로저는 함수형 언어인데, 클로저를 이용하면 다음과 같이 작성할 수 있다.

![](/assets/images/posts/222620505120/c84df4cd9845.png?type=w580)

리스프를 잘 모른다면 위의 코드가 다소 생소하게 느껴질 수도 있기 때문에 아래와 같이 나눠서 주석을 달아보자.

![](/assets/images/posts/222620505120/e8991be85c7d.png?type=w580)

println, take, map, range 분명히 모두 함수다.

  • range 함수는 0부터 시작해서 끝이 없는 정수 리스트를 반환한다.

  • 반환된 정수 리스트는 map 함수로 전달되고, 각 정수에 대해 제곱을 계산하는 익명 함수를 호출하여, 모든 정수의 제곱에 대해 끝이 없는 리스트를 생성한다.

  • 제곱된 리스트는 take 함수로 전달되고, 이 함수는 앞의 25개까지의 항목으로 구성된 새로운 리스트를 반환한다.

  • println 함수는 입력값을 출력하는데, 이 경우 입력은 앞의 25개의 정수에 대한 제곱 값으로 구성된 리스트다.

지금까지 설명한 내용이 여전히 혼란스럽다면, 클로저 언어와 함수형 프로그래밍이 무엇인지를 배우는 기회를 스스로 갖도록 하자.

자바 프로그램은 가변 변수(mutable variable)를 사용하는데, 가변 변수는 프로그램 실행 중에 상태가 변할 수 있다.

클로저에서는 x와 같은 변수가 한 번 초기화되면 절대로 변하지 않는다. 이는 우리에게 놀라운 사실을 알려준다. 함수형 언어에서 변수는 변경되지 않는다.

불변성과 아키텍처

아키텍트는 왜 변수의 가변성을 염려하는가? 터무니없게도 대답은 단순하다. 경합(race) 조건, 교착상태(deadlock) 조건, 동시 업데이트(concurrent update) 문제가 모두 가변 변수로 인해 발생하기 때문이다. 락(lock)이 가변적이지 않다면 교착상태도 일어나지 않는다.

다시 말해 우리가 동시성 애플리케이션에서 마주치는 모든 문제, 즉 다수의 스레드와 프로세스를 사용하는 애플리케이션에서 마주치는 모든 문제는 가변 변수가 없다면 절대 생기지 않는다.

아키텍트라면 동시성(concurrency) 문제에 지대한 관심을 가져야만 한다.

그렇다면, 이제 불변성이 정말로 실현 가능한지를 스스로에게 물어봐야 한다. 물어봤을 때 대답은 대체로 긍정적이다. 다만, 저장 공간이 무한하고 프로세서의 속도가 무한히 빠르다고 전제한다면 말이다. 하지만 자원에 제약이 걸린다면 불변성은 실현 가능하겠지만 일종의 타협을 해야 한다.

가변성의 분리

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

![](/assets/images/posts/222620505120/6b4195bdd9d8.png?type=w580)

상태 변경은 컴포넌트를 갖가지 동시성 문제에 노출하는 꼴이므로, 흔히 트랜잭션 메모리(transactional memory)와 같은 실천법을 사용하여 동시 업데이트와 경합 조건 문제로부터 가변 변수를 보호한다.

애플리케이션을 제대로 구조화하려면 변수를 변경하는 컴포넌트와 변경하지 않는 컴포넌트를 분리해야 한다는 것이다.

이벤트 소싱

저장 공간과 처리 능력의 한계는 우리의 시야에서 급격히 사라지고 있다. 이제 프로세서가 초당 수십억 개의 명령을 수행하고 램(RAM) 용량은 수십억 바이트인 시대가 되었다. 더 많은 메모리를 확보할수록, 기계가 더 빨라질 수록, 필요한 가변 상태는 더 적어진다.

예로, 고객 계좌 잔고 관리하는 은행 애플리케이션을 생각해볼 때,

시간이 지날수록 트랜잭션 수는 끝없이 증가하고, 잔고 계산에 필요한 컴퓨팅 자원은 걷잡을 수 없이 커진다.

이벤트 소싱에 깔려있는 기본 발생은 상태가 아닌 트랜잭션을 저장하자는 전략이다. 상태가 필요해지면 단순히 상태의 시작점부터 모든 트랜잭션을 처리한다. 물론 지름길을 택할 수도 있다. 예를 들어 매일 자정에 상태를 계산한 후 저장한다. 그 후 상태 정보가 필요해지면 자정 이후의 트랜잭션만을 처리하면 된다.

결과적으로 애플리케이션은 CRUD가 아니라 그저 CR만 수행한다. 데이터 저장소에서 변경과 삭제가 전혀 발생하지 않으므로 동시 업데이트 문제 또한 일어나지 않는다.

결론

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

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

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

도구는 달라졌고 하드웨어도 변했지만, 소프트웨어의 핵심은 여전히 그대로다.

소프트웨어, 즉 컴퓨터 프로그램은 순차(sequence), 분기(selection), 반복(iteration), 참조(indirection)로 구성된다. 그 이상 그 이하도 아니다.

This article is licensed under CC BY 4.0 by the author.