Programming

함수형 프로그래밍(FP)

AlwaysPr 2017. 12. 25. 10:24

자바 8을 사용하다 보니 자연스럽게 람다와 스트림을 통하여 함수형 프로그래밍을 접하게 되었습니다. 위 두 가지를 쓰면서도 함수형 프로그래밍에 대한 이해는 없는 채, 단순히 간결한 코드 작성이 가능하다는 이유만으로 사용을 해왔습니다. 그러다 함수형 프로그래밍을 좀 더 깊숙하게 알기 위해 스터디를 진행했습니다.

 

함수형 프로그래밍이 무엇이냐고 물어본다면 한마디로 '순수 함수(Pure Function)를 작성'하는 것이라고 정의를 내릴 수 있습니다. 이것을 풀어 말하면 '동일한 입력값에 대해서는 항상 동일한 출력값을 반환(참조에 투명)'하는 것입니다. 그러나 독자분들은 다음과 같이 생각하실 수도 있습니다. '어? 내가 짠 코드는 동일한 입력값을 넣으면 동일한 출력값이 나오던데?' 이는 저도 가졌던 오해였고, 코드를 통해 이해를 도와보겠습니다.

public class OOP {
    public static void main(String[] args) {
        Point p = new Point();
        final int value = 5;

        p.setX(10);
        p.addX(value);  // 15

        p.setX(15);
        p.addX(value);  // 20
    }
}

@Data
class Point {
    private int x;
    private int y;

    public int addX(int i){
        return x + i;
    }
}

Point라는 객체가 x, y라는 좌표를 상태로 가지고 있습니다. 그리고 OOP라는 객체는 Point에 x 값을 입력하고, x에 값을 더하는 작업을 2번 하고 있습니다.

그럼 다시 한번 돌아가서 함수형 프로그래밍의 정의에 대해 생각을 해보죠. '동일한 입력값에 대해서는 항상 동일한 출력값을 반환'한다. 그러나 위의 경우는 addX() 메소드에 동일한 값을 넣었지만, 15, 20이라는 각각의 다른 값이 나왔습니다. 함수형 스타일이 아니라는 이야기죠. 느낌이 오시나요?

 

왜 다른 결과가 나왔는지 원인을 살펴보겠습니다. 주원인은 상태를 가지고 있었다는 것이죠. addX()라는 메소드는 동일한 역할을 하고 있지만, setX()라는 함수가 객체의 상태를 변경하고 있습니다. 이것을 부작용(Side effect)이라고 표현을 합니다. 위키에서는 부작용을 다음과 같이 정의합니다. '컴퓨터 과학에서 함수가 결괏값 이외에 다른 상태를 변경시킬 때 부작용이 있다고 말한다. (중략) 부작용은 프로그램의 동작을 이해하기 어렵게 한다.' (setX() 메소드는 결괏값을 가지고 있진 않지만 비슷한 맥락으로 이해했습니다.)

 

OOP의 경우는 객체가 상태를 가지고, 상태를 조작하며 로직을 수행합니다. 그래서 부작용이 일어날 수 있죠. 그러나 함수형 프로그래밍은 상태를 가지는 것을 지양하며, 함수들로서 로직이 이루어지게 하는 것이죠. 그래서 부작용이 최소화되고, 아래와 같은 메소드 체이닝 기법을 자주 볼 수 있습니다. 

Library.method1()
       .method2()
       .method3()
       ...

 

그러면 함수형 스타일의 코드는 어떤 걸까요? 여러분이 자주 쓰는 자바 라이브러리에서도 함수형 스타일의 코드가 있다는 걸 알고 계시나요?

public final class Math {
    public static final double E = 2.7182818284590452354;
    public static final double PI = 3.14159265358979323846;


    private Math() {
    }


    public static double sin(double a) {
        return StrictMath.sin(a);
    }


    public static double cos(double a) {
        return StrictMath.cos(a); // 
    }


    public static double tan(double a) {
        return StrictMath.tan(a);
    }
}

java.lang.Math는 상태(전역변수)를 가지고 있지 않고, 함수로서 표현을 합니다. 

 

다시 처음으로 돌아가서 순수 함수에 대해서 생각해보겠습니다. 순수 함수로 작성되면 무엇이 좋을까요?

1. 부작용이 없다.

2. Thread-safe 하다.

3. 테스트하기 편리하다.

 

순수 함수로 작성하게 되면 부작용이 없고, 부작용이 없으면 Thread-safe 하고 테스트하기 편리합니다. 

 

왜 Thread-safe 하고 테스트하기 편리할까요?

상태를 가진 객체에 여러 쓰레드가 접근하여 상태를 변경하게 되면 Race Condition이 발생하여 데이터 조작에 문제가 생길 수 있습니다. (관련된 내용은 '스프링 빈은 Thread-safe 할까?'에서 자세하게 다뤘습니다) 그러나 부작용이 없게 코드를 작성하면 이러한 문제에서 자유로울 수 있습니다. 

부작용이 없다는 말 또한 동일한 입력값에 대해서는 항상 동일한 출력값을 반환을 의미합니다. 즉, 동일한 입력값에 대해 다양한 상태를 일일이 테스트할 필요가 없게 됩니다.

 

지금까지는 함수형 프로그래밍의 특징과 장점에 대해서만 이야기를 나눴는데, 그러면 어떤 단점이 있을까요?

1. 상태가 없다.

2. 어렵다.

3. 인력이 부족하다.

 

여태까지 상태가 없어서 좋은 점만을 말해왔습니다. 그러나 상태가 없음으로 인한 문제도 있습니다. 예를 들면 다음과 같은 상황인데요. 게임에서 몬스터가 공격받고 있고, 체력이 변합니다. OOP에서는 단순히 체력이라는 상태를 변경 시키면 되지만, FP에서는 체력이 변경될 때마다 몬스터를 생성하는 방식이죠. 

그리고 아직은 낯설고 생소(커링, 부분 적용, 고계 함수 등)하다 보니 어렵고, 인력이 부족한 것 같습니다.

 

마지막으로 정리를 하자면, 

1. 함수형 프로그래밍은 순수 함수로 코드를 작성하는 프로그래밍 방식입니다.

2. 순수 함수로 작성된 코드는 부작용이 없습니다.

3. 부작용이 없음으로 인한 다양한 장단점이 있습니다.

 

일반적으로 함수형 프로그래밍은 선언형 프로그래밍을 지원합니다. 추후에 관련 내용도 포스팅하도록 하겠습니다.

아래는 발표 자료입니다.