Today I Learned

[모던 자바 인 액션] 19장. 함수형 프로그래밍 기법 (1) 본문

JAVA & Spring/모던 자바 인 액션

[모던 자바 인 액션] 19장. 함수형 프로그래밍 기법 (1)

하이라이터 2021. 12. 7. 02:17
728x90

이 장의 내용

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

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

자바8에서는 ::연산자로 메서드 참조를 만들거나 람다 표현식으로 직접 함수값을 표현해서 메서드를 함수값으로 사용할 수 있다.

19.1.1 고차원 함수

Comparator.comparing처럼 다음 중 하나 이상의 동작을 수행하는 함수를 고차원 함수라 부른다.

  • 하나 이상의 함수를 인수로 받음
  • 함수를 결과로 반환

스트림 연산과 마찬가지로 고차원 함수를 구현 시, 인수로 전달된 함수가 부작용을 포함할 가능성을 염두에 두어야 한다.

19.1.2 커링

커링은 x와 y라는 두 인수를 받는 함수 f를 한 개의 인수를 받는 g라는 함수로 대체하는 기법이다.

static double converter(double x, double f, double b) {
  return x * f + b;
}
...
double gbp = converter(1000, 0.6, 0);

//커링을 활용한 팩토리로 정의
static DoubleUnaryOperator curriedConvergter(double f, double b) {
  return (double x) -> x * f + b;
}
...
DoubleUnaryOperator convertUSDtoGBP = curriedConvergter(0.6, 0);
double gbp = convertUSDtoGBP.applyAsDouble(1000);

첫 번째 함수는 인수 x, f, b를 항상 입력해줘야 한다.

두 번째 함수는 f, b 두 가지 인수만 함수에 요청하고, 반환된 함수에 인수 x를 이용해서 결과를 얻을 수 있다.


19.2 영속 자료구조

함수형 메서드에서는 전역 자료구조나 인수로 전달된 구조를 갱신할 수 없다.

자료 구조를 바꾼다면 같은 메서드를 두 번 호출했을 때 결과가 달라지면서 참조 투명성에 위배되고 인수를 결과로 단순하게 매핑할 수 있는 능력이 상실되기 때문이다.

19.2.1 파괴적인 갱신과 함수형

class TrainJourney {
  public int price;
  public TrainJourney onward; //이어지는 여정
  ...
}

static TrainJourney link(TrainJourney a, TrainJourney b) {
  if (a == nul) return b;
  
  TrainJourney t= a;
  while(t.onward != null) {
    t = t.onward;
  }
  t.onward =b;
  return a;
}

위의 함수가 수행되면 a의 값까지 갱신시켜버린다. 기존에 a를 참조하던 영역에서 문제가 생길 수 있다.

static TrainJourney append(TrainJourney a, TrainJourney b) {
  retun a == null ? b : new TrainJourney(a.price, append(a.onward, b));
}

위 코드는 기존 자료구조를 변경하지 않는다. 하지만 기존 a와 b가 바뀐다면 함께 갱신되므로 주의해야 한다.

19.2.2 트리를 사용한 다른 예제

다음은 HashMap같은 인터페이스를 구현하는 이진 트리 탐색 트리 중 update 메서드이다.

//Tree(key, val, left, right);

public static Tree update(String key, int newval, Tree t) {
  if (t == null)
    t = new Tree(key, newval, null, null);
  else if (key.equal(t.key))
    t.val = newval;
  else if (key.compareTo(t.key) < 0)
    t.left = update(key, newval, t.left);
  else
    t.right = update(key, newval, t.right);
  return t;
}

두 가지 update 버전 모두 기존 트리를 변경하고 트리에 저장된 맵의 모든 사용자가 변경에 영향을 받는다.

19.2.3 함수형 접근법 사용

public static Tree fupdate(String key, int newval, Tree t) {
  if (t == null) ?
    new Tree(key, newval, null, null) :
      k.equals(t.key) ?
        new Tree(key, newval, t.left, t.right) :
      k.compareTo(t.key) < 0 ?
        new Tree(t.key, t.val, fupdate(key, newval, t.left), t.right) :
        new Tree(t.key, t.val, t.left, fupdate(key, newval, t.right));
}

부작용 없는 하나의 표현식임을 강조하기 위해 하나의 조건문을 사용했지만, if-then-else 문으로 코드를 구현할 수도 있다.

fupdate를 호출하면 기존의 트리를 갱신하는 것이 아니라 새로운 노드를 만든다. 따라서 기존 자료구조에는 영향을 미치지 않는다.

728x90
Comments