6. 함수형 프로그래밍
함수형 프로그래밍의 핵심은 람다 계산법이다.
예제를 통해 이해해보자.
6.1. 정수를 제곱하기
함수형 프로그래밍이 무엇인지 이해하려면 간단한 예제를 통해 이해하는 것이 좋다.
아래 예제는 25까지의 정수의 제곱을 출력하는 코드이다.
1 | fun main() { |
여기에 클로저를 적용해보자.
1 | fun main() { |
단순하게 구현한 코드와 클로저를 적용한 코드는 어떤 차이점이 있을까?
times
파라미터를 한 번 넘기면 main()
함수 내에서는 절대 변경되지않는 것을 알 수 있다.
즉, 함수형 언어에서 변수는 변경되지않는다.
6.2. 불변성과 아키텍처
아키텍처를 고려할때 변수의 가변성이 왜 중요할까?
대답은 단순하다.
소프트웨어의 문제점인 경합 조건, 교착 상태, 동시성 문제 등이 모두 가변 변수로 인해 발생하기때문이다.
다시 말해, 다수의 스레드와 프로세스를 사용하는 애플리케이션의 모든 문제는 가변 변수를 배제함으로써 해결할 수 있다.
우리는 다수의 스레드와 프로세스를 사용하는 환경에서도 프로그램의 강건성을 유지해야하기 때문이다.
하지만, 이 불변성이 정말로 실현 가능한지는 또 다른 문제이다.
어느 정도의 실현과 타협은 트레이드 오프 관계인데, 이를 하나씩 살펴보도록 하자.
6.3. 가변성의 분리
불변성과 관련하여 가장 주요한 타협 중 하나는 애플리케이션 혹은 애플리케이션 내부의 서비스를 가변 컴포넌트와 불변 컴포넌트로 분리하는 일이다.
불변 컴포넌트에서는 순수하게 함수형 방식으로의 처리가 가능하며, 어떠한 가변 변수도 사용되지않는다.
하지만 상태는 변경되어야할 수 있으므로 불변 컴포넌트는 변수의 상태를 변경할 수 있는 하나 이상의 다른 컴포넌트와 서로 통신하게 된다.
아래 그림을 통해 이해해보자.
상태의 변경은 컴포넌트를 여러 동시성 무네제 노출시킨다.
이를 위해 트랙잭션 메모리와 같은 실천법을 사용하여 동시성 문제와 경합 조건 문제로부터 가변 변수를 보호해야 한다.
트랜잭션 메모리는 데이터베이스가 디스크의 레코드를 다루는 방식과 동일한 방식으로 메모리의 변수를 처리한다.
즉, 트랙잭션 그 자체의 사용이나 재시도 기법 등을 통해 변수를 보호한다.
JVM 계열의 언어에서는 이 원자성을 보장하기위해 Atomic
개념을 사용하기도 한다.
6.4. 이벤트 소싱
극단적으로 생각해, 무한한 메모리와 매우 빠른 CPU를 가지고 있다고 가정해보자.
이 상황에서 우리는 막대한 고객들이 존재하는 은행 애플리케이션을 개발했다.
이 고객들은 수시로 입출금을 반복하며, 현재 잔고를 조회하기도 한다.
무한한 컴퓨팅 자원이 있다면 계좌의 생성부터 발생한 모든 입금 및 출금에 대한 트랜잭션을 보관하여 요청이 올때마다 모든 트랙잭션을 다시 계산하여 결과를 돌려줄 수 있다.
이론적으로는 가변 변수가 하나도 없는 완벽한 방법이다.
당연하겠지만 전제조건이 무한한 컴퓨팅 자원이므로 말도안되는 전략이다.
다만 제한적으로 생각하여 애플리케이션의 생명주기 동안만 문제없을 정도의 컴퓨팅 파워를 가지게끔 설계할 수 있다.
이게 바로 이벤트 소싱(event sourcing) 기법의 기본 발상이 되었다.
이벤트 소싱은 상태가 아닌 트랜잭션 그 자체를 저장하는 전략이며, 필요시 저장되어있던 모든 트랜잭션을 처리한다.
결과적으로 CRUD중 CR만 수행하고 데이터 저장소의 변경과 삭제가 전혀 발생하지않으므로 동시성 문제에서도 해방된다.
제한했다하더라도 말도 안되는 전략이라고 생각할 수 있다.
하지만 널리 쓰이는 소스 코드의 버전 관리 시스템이 이 방식으로 동작하고 있음을 유념하자.