함수형

18.1 시스템 구현과 유지보수

함수형 프로그래밍이 제공하는 부작용 없음(no side effect)과 불변성(immutability)이라는 개념은 시스템의 유지보수에 큰 도움을 준다.

18.1.1 공유된 가변 데이터

공유된 가변 데이터 구조로 인해 변수가 예상하지 못한 값을 갖게 되고 데이터 갱신 사실을 추척하기 어려워진다.

클래스 또는 객체의 상태를 바꾸지 않으며 return 문을 통해서만 자신의 결과를 반환하는 메서드를 순수(pure) 메서드 또는 부작용 없는(no side effect) 메서드라고 부른다.

여기서 부작용은 함수 내에 포함되지 못한 기능을 의미한다.

  • 자료구조를 고치거나 필드에 값을 할당(setter 메서드 같은 생성자 이외의 초기화 동작)

  • 예외 발생

  • 파일에 쓰기 등의 I/O 동작 수행

부작용 없는 시스템 컴포넌트에서는 메서드가 서로 간섭하지 않으므로 잠금을 사용하지 않고도 멀티코어 병렬성을 사용할 수 있다.

18.1.2 선언형 프로그래밍

'어떻게(how)' 구현할 것인지 집중하는 프로그래밍 형식은 고전적 객체지향 프로그래밍에서 이용하는 방식이다.

명령형 프로그래밍이라고 부르기도 한다.

Transaction mostExpensive = transactions.get(0);
if (mostExpensive == null)
  throw new IllegalArgumentException("Empty list of transactions");
  
for(Transaction t: transactions.subList(1, transactions.size())) {
  if (t.getValue() > mostExpensive.getValue()) {
    mostExpensive = t;
  }
}

'무엇을' 에 집중하는 방식도 있다. 선언형 프로그래밍이라고 부르며, 우리가 원하는 것이 무엇이고 시스템이 어떻게 그 목표를 달성할 것인지 등의 규칙을 정한다.

Optional<Transaction> mostExpensive =
  transactions.stream().max(comparing(Transaction::getValue));

18.1.3 왜 함수형 프로그래밍인가?

함수형 프로그래밍은 선언형 프로그래밍을 따르며, 부작용이 없는 계산을 지향한다.

따라서 쉽게 시스템을 구현하고 유지보수하는 데 도움을 준다.


18.2 함수형 프로그래밍이란 무엇인가?

'함수란 무엇인가?'

함수형 프로그래밍에서 함수란 수학적인 함수와 같다.

0개 이상의 인수를 가지며, 한 개 이상의 결과를 반환하지만 부작용이 없어야한다.

수학적 함수에서는 인수가 같다면 반복적으로 호출했을 때 항상 같은 결과가 반환된다.

  • 순수 함수형 프로그래밍 - '함수 그리고 if-then-else 등의 수학적 표현만 사용'하는 방식

  • 함수형 프로그래밍 - 시스템의 다른 부분에 영향을 미치지 않는다면 내부적으로는 함수형이 아닌 기능도 사용하는 방식

18.2.1 함수형 자바

자바에서는 순수 함수형이 아닌 함수형 프로그래밍을 구현한다.

함수나 메서드는 지역변수만을 변경해야 하며, 참조하는 객체가 있다면 불변객체여야 한다.

메서드 내에서 생성한 객체의 필드는 갱신할 수 있지만, 외부에 노출되어서는 안된다.

또한 함수나 메서드가 어떤 예외도 일으키지 않아야 한다. 예외가 발생하면 return으로 결과를 반환할 수 없게 되기 때문이다.

어떤 수를 0으로 나눈다든가 하는 함수의 경우 예외가 필요할 수 있다. 이럴 경우 Optional을 사용하면 예외 없이도 연산을 성공적으로 수행했는지 확인할 수 있다.

그리고 비함수형 동작을 감출 수 있는 상황에서만 부작용을 포함하는 라이브러리 함수를 사용해야 한다.

18.2.2 참조 투명성

'부작용을 감춰야한다'라는 제약은 참조 투명성 개념으로 귀결된다.

같은 인수로 함수를 호출했을 때 항상 같은 결과를 반환한다면 참조적으로 투명한 함수라고 표현한다.

List를 반환하는 메서드를 두 번 호출한다고 가정하자.

결과 리스트가 가변 객체라면 반환된 두 리스트는 같은 객체라 할 수 없다. 따라서 이 메서드는 참조적으로 투명한 메서드가 아니다.

하지만 결과 리스트를 불변 값으로 사용할 것이라면 두 리스트는 같은 객체라 볼 수 있으므로 참조적으로 투명한 것으로 간주할 수 있다.

18.2.3 객체지향 프로그래밍과 함수형 프로그래밍

자바에서는 함수형 프로그래밍과 기존의 익스트림 객체지향 프로그래밍이 혼합되어 사용된다.

예를들어 Iterator로 가변 내부 상태를 포함하는 자료구조를 탐색하면서 함수형 방식으로 자료구조에 들어있는 값의 합계를 계산할 수 있다.


18.3 재귀와 반복

반복문 내부에서 가변

이론적으로 반복을 이용하는 모든 프로그램은 재귀로도 구현할 수 있는데 재귀를 이용하면 변화가 일어나지 않는다.

//반복 방식의 팩토리얼
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);
}

첫 번째 방식은 매번 변수 r과 i가 갱신되지만 두 번째는 재귀를 통해 수학적으로 문제를 해결한다.

스트림을 사용하면 더 단순하게 팩토리얼을 정의할 수 있다.

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

Last updated