전의 글에 이어서 변수 없는 프로그램을 짜는 방법을 알아보도록 하자. 먼저 가장 간단한 응용사례인

팩토리얼 (1*2*3*4*..*n)을 구하는 프로그램을 생각해보자

명령형 언어의 경우는 다음과 같은 코드를 통상적으로 짜게 된다

int factorial(int n)
{
    int f = 1;
    for (int i=2; i<n; ++i)
        f *= i;
    return f;
}

루프를 쓰기 위해서는 루프 카운터 역할을 하는 i 가 변수로 지정되어야 하고, 곱한 값을 누적시킬 f 가

변수로 존재하여야 한다. 이것을 변수 없이 어떻게 짤 수 있을까? 그 해답은 재귀호출(Recursion)이다.


함수형 프로그래밍에서 재귀호출은 알파와 오메가다. 재귀호출 없이는 함수형 프로그래밍이 불가능하며

재귀호출을 이용하면 명령형 프로그래밍에서 사용하던 임의의 루프 처리를 완벽하게 구현할 수 있다

Erlang 으로 작성한 팩토리얼을 구하는 소스는 다음과 같다.

factorial(0) ->
    1;
factorial(N) ->
    N * factorial(N-1).

보다시피 변수는 하나도 없이 인자값과 리턴값, 재귀적 함수호출을 이용하여, 루프와 변수를 대체할 수

있다.  이 시점에서 C++같은 환경에 익숙해져 있던 프로그래머라면, 다시 한 번 거부감이 몰려올 것이다.


뭣이? 루프를 쓰려면 무조건 재귀 함수 호출을 해야 한다고!? 함수호출 한번 할 때마다 스택에 값을 넣고

call 을 해야 하는데, 매번 그 비효율을 감수해야 한단 말인가?


물론 그렇지는 않다. Tail call optimization (TCO)이란 것이 있어서, 함수 내용의 맨 마지막이 다시 함수

호출이면 자동으로 함수호출을 루프로 바꿔주는 기능이 있다. 단, 조건이 지켜지지 않으면 최적화는

수행되지 못한다. 위의 경우는 factorial 함수내용의 맨 마지막이 factorial(N-1) 이니 최적화에 해당될

것 같아 보이지만, 사실 맨 마지막으로 평가되는 부분은 그 앞에 있는 곱셈이다. 그래서 위와 같은 문장은

최적화가 가능한 형태로 바꿔줘야 한다.

fac(0, Acc) ->
   Acc;
fac(N, Acc) ->
   fac(N-1, N*Acc);
factorial(N) ->
   fac(N, 1).

위와 같은 방식으로 함수를 쪼개놓으면 fac 을 호출하는 것이 확실히 맨 마지막이 되기 때문에 컴파일러

는 효율적인 코드를 만들어줄 수 있다.


위의 경우는 전혀 변수를 쓰지 않은 순수한 형태의 함수 (pure function)의 사용예이다. pure function

은 다시 엄밀히 정의하자면 (http://en.wikipedia.org/wiki/Pure_function) 두가지 요소로 정리된다.

- 같은 인자값이 들어오면 항상 같은 값을 리턴한다.
   함수의 결과는 인자값외에는 어떠한 요소에도 영향받지 않는다
- 함수를 수행함에 있어서 어떠한 부수적 현상(Side Effect)도 발생하지 않는다. 화면에 글씨를 쓰는 것
    부터, 함수 외부의 메모리에 값을 쓰는 것, 그림을 그리는 것, 소켓에 값을 보내는 것 등 일체의 활동은
    Side Effect 로 간주한다

아무리 함수형 언어라고 해도 모든 함수를 Pure Function으로만 만들지는 않는다. 왜냐하면 최소한의

유저와의 인터렉션을 하려면 값을 받아들인다던가, 화면에 결과를 출력한다던가 하는 행위는 해야 하기

때문이다. 함수형 프로그래밍의 핵심은 로직을 최대한 Pure function과 Impure function으로 구분하자는

것이다. 최대한 Pure function 을 많이 확보할 수록 프로그래머에게는 이득이 된다.



도대체 Pure function의 이득이 뭐길래 이 불편을 감수하는 것일까?

1. Pure function 은 기본적으로 멀티쓰레드 상황에서 호출하기에 안전하다.
- 값을 바꾸거나 외부에 영향을 주는 요소가 없으니 Race Condition이 일어날 일이 없고, Race Condition
    이 생기지 않으니 Lock을 해야 할 필요도 없다. Pure function은 몇개의 쓰레드가 동시에 진입하건
    아무 문제 없이 병렬적으로 수행이 가능하다

2. Pure function 은 Test 하기 좋다 (or Pure function 은  재사용성이 높다)
- 테스트하기 용이한 코드는, 같은 조건에선 항상 같은 결과를 내놓는 함수이고 이것은 바로 Pure
    function을 지칭하는 말이기도 하다.
- 테스트하기가 좋기 때문에 떼어다 쓰기도 좋고, 결국 재사용성이 좋다는 것을 의미한다
- 함수형 언어로 프로그램을 짠다는 것은 기본적으로 bottom up 프로그래밍을 의미한다. 함수를 하나씩
   만들어놓고 다시 그 함수들을 조합해가면서 좀 더 복잡한 함수를 만들어가면서 프로그램을 짜는
   것이다.

3, Pure function 은 Debug 하기 좋다
- 흔히 하는 비유로, 프로그래밍에서 변하는 값은, 기계공학에서 moving part (구동부분) 라고도 한다.
    구동 부분이 적을 수록, 기계의 내구성은 좋아지기 때문에 어떻게 하면 구동 부분이 적게 설계할
    것인가는 기계공학의 중요한 목표다. 마찬가지로 우리가 짠 프로그램에서 문제가 발생했다면, 뭔가
    변하지 말았어야 할 것이 변했거나, 엉뚱한 것을 변화시켰기 때문인 경우가 많다.
- Pure function 은 시작조건과 종료조건이 짧고 명료하기 때문에 의외의 곳에서 문제가 튀어나올
   원인이 근본적으로 차단되어 있다.

멀티쓰레드에도 안전하고, 재사용성도 높고, 디버깅하기도

imcgames 의 김학규입니다