왜 함수형 프로그래밍이 좋을까?

함수형 프로그래밍이란?

일반적인 정의를 내리자면, 프로그래밍의 여러 패러다임 중 하나이며, 함수를 이용하여 프로그래밍 하는 것을 의미한다.
John Hughes 는 1990년에 쓴 "Why Functional Programming Matters" 에서 다음과 같이 설명하였다.

함수형 프로그램은 대입문이 없어 변수에 값이 할당되면 그 이후 절대 변하지 않는다.
좀 더 일반적으로 말하자면, 함수형 프로그램은 부수 효과(side effect)가 전혀 없다. 함수 호출은 그 함수의 결과를 계산하는 것 외에 다른 효과는 없기 때문에, 버그의 주요 원인을 제거할 수 있고, 함수의 실행 순서는 중요하지 않게 된다.
표현식(expression: 이는 함수들의 조합이라 생각할 수 있다.)의 결과를 바꿀 수 있는 부수 효과가 없기 때문에 그 표현식은 아무때나 실행될 수 있다. 이것은 흐름의 제어를 지정해야 하는 프로그래머의 짐을 덜어준다.
표현식은 아무때나 실행될 수 있기 때문에 변수를 그 표현식의 값으로 대체할 수 있고, 반대로 값을 그 표현식으로도 대체할 수도 있다. 이러한 프로그램을 두고 "참조 투명성을 가졌다"라고 한다. 이러한 특성은 함수형 프로그램이 기존의 다른 프로그램보다 더 수학적으로 다루기 쉽게 한다.

함수형 프로그래밍을 접한지 얼마되지 않았고, 이제 배우려는 사람들은 아마도, 위에서 설명한 내용을 이해하기 어려울 것이다. 사실 함수형 프로그래밍에 대해 이해하기 쉽게 한마디로 풀어내는건 그 누구에게라도 쉬운 일은 아닐 것이다.
위 내용을 이해하기 어렵다면, 우선, 함수로만 이루어진 프로그램..이라고 생각하자.

함수형 프로그래밍은 다음과 같은 테크닉들을 사용하는 것이라고 여겨지기도 한다. (여기에서, 아래 테크닉들에 대해서 일일이 설명하진 않는다.)

  • 일급 함수
  • 익명 함수
  • 클로져
  • 커링
  • 게으른 평가
  • 대수적 데이터 타입

대부분의 함수형 프로그래밍 언어들은 위의 테크닉들을 사용한다. 하지만 이러한 프로그래밍 언어가 함수형 프로그램을 만드는 것은 아니며, 그것보다는 얼마나 코드를 더 함수형 답게 작성하냐에 달려있다고 생각한다. 최근 자바나 자바스크립트를 이용한 함수형 프로그래밍에 대한 자료들(책, 아티클 등)을 심심치않게 찾아볼 수 있는 것만 봐도 그렇다. (물론, 함수형 프로그래밍에 필요한 최소한의 기본적인 기능이 있어야하겠지만...)

그럼, 함수형스러운(?) 코드는 어떤 코드일까? 함수형 프로그래밍을 공부하다보면, 부수 효과(side effect)라는 말을 많이 듣고, 보게 되는데 이 부수 효과의 유무가 아마도, 명령형 프로그래밍과 함수형 프로그래밍의 가장 큰 차이이지 않을까 생각한다.

"부수 효과가 없다" 라는 말이 무슨 의미일까? 좀 더 자세히 생각해보자.

부수효과 없는 프로그래밍?

함수형 프로그램은 함수들의 조합으로 만들어지며, 각 함수들은 인자를 받고, 그에 따른 결과를 내놓을 뿐, 함수 내부적으로 어떠한 상태도 가지지 않는다. 때문에, 함수 내부에서 벌어지는 일에 대해서는 전혀 신경 쓸 필요가 없다. 단지, 함수 호출 시 입력하는 값과 그에 대한 결과 값만이 중요할 뿐이다.

좀 더 풀어쓴다면.. 부수 효과가 없다는 것은 다음과 같은 의미를 가진다.

  • 변수의 변형이 없다. 사실, 변수를 사용하지 않는다.
  • 어떤 함수에 특정 값을 입력하면 그에 따른 출력이 발생하며, 같은 값을 입력하면 항상 그에 따른 출력도 같다.
    • 함수의 출력은 오로지 그 함수에 입력된 값에만 의존성을 가진다는 의미이다.
    • 예를 들어, f(x) = x + 2 라는 함수가 있다고 했을 때, 입력이 3 이면, 이 함수의 출력은 항상 5 이어야 한다.
  • 콘솔 또는 어떤 디바이스에도 출력(printing)하지 않는다.
  • 파일, 데이터베이스, 네트워크 어디에도 데이터를 쓰지(writing) 않는다.
  • 예외가 발생하지 않는다.

응? 근데 좀 이상하다.. 출력도 하지 않고, 쓰지도 않으면 어플리케이션을 만들 수 있나? 사실, 불가능하다.. 순수한 함수인 경우에 그렇다는 말이지, 현실 세계에서 부수 효과가 전혀 없는 프로그램을 만든다는 것은 있을 수 없는 일이다.
다만, 이러한 부수 효과는 일련의 프로세스가 진행되는 중간에 발생하는 것이 아니라 모든 연산이 종료되는 시점에 발생하는 것이 좋다.

다음의 코드를 보고 부수 효과의 유무에 대해 판단해보자.

public static int div(int a, int b) {  
    return a / b;
}

위 코드는 b=0 일 때, 예외가 발생하기 때문에 부수 효과가 있는 코드이다.
반면, 아래 코드는 결과값이 이상하겠지만, 예외가 발생하지 않고 같은 입력에 항상 같은 값을 출력하기에 부수 효과가 없는 코드이다.

public static int div(int a, int b) {  
    return (int) (a / (float) b);
}

아래 코드는 log 를 쓰고 있기에 당연히 부수 효과가 있는 코드이다.

public static int add(int a, int b) {  
    log(String.format("Adding %s and %s", a, b));
    while (b > 0) {
        a++;
        b--;
    }
    log(String.format("Returning %s", a));
    return a;
}

여기까지 조금 이해 했다면, 이 글의 주제인 "함수형 프로그래밍이 왜 중요한가?"에 대해 얘기해보자.

왜 함수형 프로그래밍이어야 할까?

동시성 프로그래밍

"반도체 회로의 성능은 18개월마다 2배씩 빨라진다"는 무어의 법칙에 대해 한번쯤은 들어봤을 것이다. 그리고, 이 법칙이 2000년대 중반쯤 깨졌다는 것도 알고 있을 것이다.
반도체 성능 향상의 한계에 다다르자, 최근 CPU 회사들은 하나의 칩 속도를 높이는 방식 대신, 여러 개의 칩이 병렬적으로 동작하도록 하여 성능을 높이는 방식을 선택하고 있으며, 이로 인해 어플리케이션 코드는 멀티스레드를 이용하여, CPU 의 코어를 최대한 활용해야만 하는 환경이 되었다. Herb Sutter 는 "공짜 점심은 끝났다"라는 유명한 말로 이러한 환경에 대해 설명하기도 하였다.

명령형 프로그래밍(Imperative programming) 에서 멀티스레드를 활용한 동시성 프로그래밍은 개발자들을 아주 힘들게 하는 것 중 하나다. 그 유명한 "식사하는 철학자들"의 문제에서 보듯이 동시성 프로그래밍은 교착상태(deadlock)에 빠질 위험이 존재하며, 물론 해결책은 있지만 복잡하기도 하고 고려해야할 요소들이 많다.

명령형 프로그래밍에서 이러한 문제가 발생하는 주 원인은 스레드 간에 공유되는 데이터나 상태 값이 변경 가능(mutable)하기 때문이다.
하지만, 함수형 프로그래밍에서는 사용하는 모든 데이터가 변경 불가능(immutable)하고 함수는 부수 효과를 가지고 있지 않다. 때문에, 여러 스레드가 동시에 공유 데이터에 접근하더라도 해당 데이터가 변경될 수 없기 때문에 동시성과 관련된 문제를 원천적으로 봉쇄한다.

그외 장점들

함수형 프로그래밍의 중심은 함수며, 이러한 함수들이 가지는 특징들 덕분에 프로그래밍에 꽤나 유용한 장점들을 제공해준다.

함수는 입력과 그에 따른 출력이 있을 뿐이다

함수는 내부적으로 어떠한 상태값도 가지지 않으며, 단지 입력에 대한 출력만을 책임질 뿐이다. 이러한 특징 덕분에 코드를 테스트하기 쉽고, 함수가 하는 일이 명확하기 때문에 함수의 시그니처(signature)를 좀 더 의미있게 정할 수 있고, 이는 코드를 읽기 좋게 하는 장점을 가지게 한다.

함수는 값(value)처럼 쓰일 수 있다

함수가 값처럼 쓰일 수 있기에 어떤 함수는 입력 또는 출력이 함수가 될 수 있다. 이러한 함수를 고차함수(higher-order function)라 하는데, 이 덕분에 함수들의 결합, 조합이 쉬워질 뿐만 아니라, 좀 더 간결한 코드를 만들 수 있다. 익명함수를 사용할 수 있다는 점도 이런 장점을 배가시킨다.

아래 코드는 1~100까지의 합을 구하는 코드를 자바7 이전 버전으로 구현한 코드와 자바8 버전(고차함수와 익명함수 사용)으로 구현한 코드를 비교한 것으로 위 설명을 이해하는데 도움이 된다.

public static int evenSum() {  
   int sum = 0;
   for (int i = 1; i <= 100; i++) {
      if ( i % 2 == 0)
         sum += i;
   }
   return sum;
}
public static int evenSum() {  
   return IntStream.rangeClosed(1, 100)
         .filter(i -> i % 2 ==0)
         .reduce(0, (l, r) -> l + r);
}

마치며

함수형 프로그래밍이란 패러다임이 생긴지는 꽤나 오래되었다. 그 동안 명령형 프로그래밍이나 OOP 에 비해 주목을 받지 못하다가, "공짜 점심"이 끝나고, 처리해야 할 데이터나 트래픽은 기하급수적으로 증가하는 최근 상황에서야 주목을 받는 것 같다. Lisp, Haskell 같은 오래된 언어 뿐만 아니라, Scala, Elm, Elixr 등 새로운 함수형 프로그래밍 언어들까지 많은 개발 언어들이 존재하고, Java나 C# 같은 OOP 언어들에도 함수형 언어 특성들이 추가되는걸 보면 함수형 프로그래밍이 개발자들에게 좋은 영향을 주는 것은 명확한 듯 보인다.

국내도 함수형 프로그래밍에 대해 조금씩 관심을 가지는 개발자가 늘고 있는 것 같지만, 아직까진 그 정도가 그리 크진 않은 것 같다. 개인적으로 함수형 프로그래밍에 대해 아주 해박한 지식이나 많은 경험을 가지고 있지는 않지만, 조금씩 그 매력을 알아가고 있으며, 이를 좀 더 탐구하고 싶다는 욕심도 생긴다. 최대한 이곳을 통해 관련되어 내가 새롭게 학습한 내용이나 경험들을 공유할 수 있었으면 좋겠다.

참고 자료