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.