본문 바로가기
자바

[모던 자바 인 액션] 함수형 관점으로 생각하기

by __Minnie_ 2025. 5. 28.
함수형 프로그래밍이란 무엇인가

 

함수형 프로그래밍이란 "함수를 이용하는 프로그래밍"이다. 여기서 함수는 부작용이 없어야 한다. 함수 그리고 if-then-else 등의 수학적 표현만 사용하는 것을 순수 함수형 프로그래밍이라고 하고, 시스템의 다른 부분에 영향을 미치지 않는다면 내부적으로는 함수형이 아닌 기능도 사용하는 방식을 함수형 프로그래밍이라고 한다. 

 

여기서 말하는 부작용이란 자료구조를 고치거나 값을 할당하는 것, 예외 발생, 파일에 쓰기 같은 IO 동작을 수행하는 것 등을 의미한다. 즉, 부작용 없음이란 자신을 포함하는 클래스의 상태, 다른 객체의 상태를 변경하지 않고 return문을 통해서 자신의 결과를 반환하는 것을 의미한다. 

 

함수형 프로그래밍은 부작용 없음을 통해서 시스템 구현과 유지보수의 측면에 있어서 도움을 준다. 

 

함수형 자바

자바에서는 완벽한 순수 함수형 프로그래밍을 제공하기가 어렵다. 그 이유는 자바에서 제공하는 기본 라이브러리에서 이미 부작용 메서드가 포함되어 있기 때문이다. 

 

그래서 자바에서는 순수 함수형 프로그래밍이 아니라, 함수형 프로그래밍을 구현해야 한다. 실제 부작용이 있지만 아무도 이를 보지 못하게 함으로써 부작용 없음을 달성한다. 

 

참조투명성

 

부작용이 없다는 것은 참조투명성의 개념과 동일하다. 즉, 같은 인수로 함수를 호출했을 때, 항상 같은 결과를 반환해야 한다. 여기서 한가지 문제가 되는 점이 있는데, 만약 결과로 List를 반환하는 함수가 있다고 했을 때, 이 함수를 2번 호출하게 되면 값은 동일한 List가 반환되어도 이 두 List는 서로 다른 메모리 공간에 생성된 객체일 것이다. 그렇다면 우리는 이를 같은 결과라고 할 수 있는가? 이 경우 우리는 List가 가변 객체이더라도 순수값으로 사용할 것이라면 두 리스트를 같은 객체로 볼 수 있다. 따라서, 이 경우에도 참조투명성이 지켜지는 것이라고 볼 수 있다.

 

 

 

재귀와 반복

 

우리는 보통 while, for문과 같은 반복문을 사용하게 되면 조건문을 갱신하게 되는데, 이 변화를 다른 누군가가 알아차리지 못한다면 상관은 없다. 하지만 반복문을 돌면서 인자로 받은 값을 변경하는 경우에는 부작용이 발생하게 된다. 그래서 순수 함수형 프로그래밍에서는 이런 부작용 연산을 제거하였는데, 우리는 반복문을 사용하는 함수는 재귀로 구현할 수 있다. 

 

간단하게 팩토리얼 예제를 반복문 형식, 재귀형식, 스트림 형식으로 확인해보자

static int factorialIterative(int n) {
	int r = 1;
    for (int i = 1; i <= n; i++) {
    	r *= i;
    }
    
    return r;
}


static long factorialRecursive(long n) {
	return n == 1? 1 : n * factorialRecursive(n - 1)

}

static long factorialStreams(long n ) {
	return LongStream.rangeClosed(1, n)
    			     .reduce(1, (long a, long b) -> a * b);
}

 

반복문 형식에서는 매 반복시에 r, i의 값에 변경이 생긴다. 반면, 재귀의 경우에는 변수값에 변경이 생기는 경우가 없고, 더 수학적으로 문제를 해결한 것을 확인할 수 있다.

 

그러나, 일반적으로 재귀의 경우에는 단순 반복문보다 cost가 더 많이 든다. 재귀는 호출시마다 스택 프레임이 만들어지기 때문이다. 꼬리 재귀를 사용하면 이런 문제를 어느정도 해결할 수 있다. 

 

이렇게 꼬리 재귀를 활용하면 순수 함수형을 유지하면서도 효율성까지 얻을 수 있다.