모던 자바 인 액션 - 19장 함수형 프로그래밍 기법

Modern Java In Action 정리 - 함수형 프로그래밍 기법

  • 일급 시민, 고차원함수, 커링, 부분 적용
  • 영속 자료구조
  • 자바 스트림을 일반화하는 게으른 평가와 게으른 리스트
  • 패턴 매칭, 자바에서 패턴 매칭을 흉내 내는 방법
  • 참조 투명성과 캐싱

모던 자바 인 액션 책을 보고 정리한 글입니다.

함수는 모든 곳에 존재한다.

  • 함수형 프로그래밍이란 함수나 메서드가 수학의 함수처럼 동작함을, 즉 부작용 없이 동작함을 의미했다.
  • 함수형 프래그래밍에서는 함수를 마치 일반값처럼 사용해서 인수로 전달하거나, 결과로 반환받거나, 자료구조에 저장할 수 있음을 의미한다.
  • 일반값처럼 취급할 수 있는 함수를 일급함수라고 한다.

고차원 함수

  • 지금까지 함숫값을 스트림의 처리연산으로 전달하거나, 메서드 참조를 활용하여 전달하는 등 동작 파라미터화를 달성하는 용도로만 사용해봤다. 이는 함수값 뢀용의 일부에 불과하다.
  • 함수를 인수로 받아 새로운 함수를 만드는 정적 메서드 Comparator.comparing 을 생각해보자.
1
2
3
4
5
Comparator<Apple> c = comparing(Apple::getWeight);

// 함수를 조립하여 연산 파이프라인을 만들 때 사용.
Function<String, String> transformationPipeline = addHeader.andThen(Letter::checkSplelling)
                                                           .andThen(Letter::addFooter);
  • Comparator.comparing 처럼 다음 중 하나 이상의 동작을 수행하는 함수를 고차원 함수라 부른다.
    • 하나 이상의 함수를 인수로 받음
    • 함수를 결과로 반환

커링

  • 함수를 모듈화 하고 코드를 재사용하는데 도움을 주는 커링을 살펴보자.
  • 변환요소와 기준치 조정 요소가 단위 변환 결과를 좌우하는데 섭씨를 화씨로 변환하는 예제를 가지고 커링에 대해 살펴보자.
  • CtoF(x) = x * 9/5 + 32
    1. 변환 요소를 곱함
    2. 기준치 조정 요소를 적용
  • 다음과 같이 메서드로 변환 패턴을 표현 할 수 있다.
1
2
3
static double converter(double x, double f, double b) {
  return x * f + b;
}
  • 여기서 x는 변환하려는 값이고, f는 변환 요소, b는 기준치 조정 요소이다. 온도뿐아니라 키로미터와 마일 등의 단위도 변환 해야 할 것이다.
  • 세 개의 인수를 받는 converter를 매번 만들어 해결하는 방법도 있지만, 함수를 활용하여 로직을 재활용 해보자.
  • 기존 로직을 활용하여 변환기를 특정 상황에 적용할 수 있는 방법이 있다. 다름은 커링이라는 개념을 활용하여 한 개의 인수를 갖는 변환 함수를 생산하는 팩토리를 정의하는 코드이다.
1
2
3
static DoubleUnaryOperator curriedConverter(double f, double b) {
  return (Double x) -> x * f + b;
}
  • 위 메서드에 변환요소와 기준치만 넘겨주면 원하는 작업을 수행할 함수가 반환된다.
1
2
3
4
5
DoubleUnaryOperator convertCtoF = curriedConverter(9.0/5, 32);
DoubleUnaryOperator convertUSDtoGBP = curriedConverter(0.6, 0);
DoubleUnaryOperator convertKmtoMi = curriedConverter(0.6214, 0);

convertUSDtoGBP.applyAdDouble(1000);
  • 결과적으로 기존의 변환 로직을 재활용하는 유연한 코드를 얻었다.
  • 커링은 x와 y라는 두 인수를 받는 함수 f를 한 개의 인수를 받는 g라는 함수로 대체하는 기법이다.

영속 자료구조

  • 함수형 프로그램에서는 함수형 자료구조, 불변 자료구조 등의 용어도 사용하지만 보통은 영속 자료구조라고 부른다. (여기서 영속은 DB에서 사용하는 영속과는 다른 뜻이다.)
  • 함수형 메서드에서는 전역 자료구조나 인수로 전달된 구조를 갱신할 수 없다. 자료구조를 변경한다면 같은 메서드를 두 번 호출했을 때 결과가 달려지므로 참조 투명성에 위배된다.
  • 결과 자료구조를 바꾸지 말라 라는것이 자료구조를 사용하는 모든 사용자에게 요구하는 단 한가지 조건이다.

스트림과 게으른 평가

  • 스트림은 단 한번만 소비할 수 있다는 제약으로 인해 어떤 문제가 발생했는지 살펴보자.

자기 정의 스트림

  • 게으른 평가
    • 특정한 작업이 일어나야 하는 상황에서만 스트림을 평가한다. (lazy evaluation)

게으른 리스트

  • Java 8의 스트림은 게으르다. 라는 말을 들어본적이 있을것이다. Java 8의 스트림은 요청할 때만 값을 생성한다. 스트림에게 특정 연산이 주어진다면 곧바로 연산이 수행되는것이 아니라 스트림에게 최종 연산을 적용하는 시점에 실제 연산이 이루어진다.

패턴 매칭

  • 일반적으로 함수형 프로그래밍을 구분하는 또 하나의 특징으로 패턴매칭을 들 수 있다.(정규표현식의 의미가 아니다.)
  • 자바에서 지원하지는 않지만 Kotlin과 같은 언어에서는 지원하는 기능이다.

정리

  • 일급 함수란 인수로 전달하거나, 결과로 반환하거나, 자료구조에 저장할 수 있는 함수다.
  • 고차원 함수란 한 개 이상의 함수를 인수로 받아서 다른 함수를 반환하는 함수다. 자바에서는 comparing, andThen, compose 등의 고차원 함수를 제공한다.
  • 커링은 함수를 모듈화하고 코드를 재사용할 수 있도록 지원하는 기법이다.
  • 영속 자료구조는 갱신될 때 기존 버전의 자신을 보전한다. 결과적으로 자신을 복사하는 과정이 따로 필요하지 않다.
  • 자바의 스트림은 스스로 정의할 수 없다.
  • 게으른 리스트는 자바 스트림보다 비싼 버전으로 간주할 수 있다. 게으른 리스트는 데이터를 요청했을 때 Supplier를 이용하여 요소를 생성한다. Supplier는 자료구조의 요소를 생성하는 역할을 수행한다.
  • 패턴 매칭은 자료형을 언랩하는 함수형 기능이다. 자바의 switch문을 일반화할 수 있다.
  • 참조 투명성을 유지하는 상황에서는 계산 결과를 캐시할 수 있다.
  • 콤비네이터는 둘 이상의 함수나 자료구조를 조합하는 함수형 개념이다.