본문 바로가기
Java

Optional 형태가 가지는 의미(Monad)

by ybs 2021. 5. 6.
반응형

Monad 에 대한 정확한 개념을 다루진 않고, 자바 Optional 에서 사용하는 형태를 Monad 응용 관점에서 살펴보려고 한다. Monad 는 다양한 응용이 존재하고 Optional 는 그중 하나다(Optional 을 maybe monad 라고도 한다).

 

Optional 에 대한 용어를 정리하면 아래와 같다.

 

Parameterised type : Optional<T>

- T : Type parameter

- Optional : Wrapper Type

 

즉, Optional<T> 는 T type 이 Optional context 에 의해 wrapped 되었다고 말한다.

 

Optional 객체는 of 메서드를 통해서 만들 수 있는데 코드는 아래와 같다.

public final class Optional<T> {

	private final T value;

	private Optional(T value) {
		this.value = Objects.requireNonNull(value);
	}

	public static <T> Optional<T> of(T value) {
		return new Optional<>(value);
	}
}

cf) of 메서드를 Monad 관점에서는 unit 이라고 한다.

 

이제 두개의 Optional<Integer> 객체를 더하는 연산을 해보자.

private Optional<Integer> optionalAdd(Optional<Integer> o1, Optional<Integer> o2) {
	if (o1.isPresent() && o2.isPresent()) {
		return Optional.of(o1.get() + o2.get());
	}

	return Optional.empty();
}

가장 먼저 쉽게 생각할 수 있는 방법은 Optional 객체에 값이 있으면(isPresent 메서드로 확인) get 메서드를 통해 값을 가져와 더하는거다.

이 방법은 Optional context 를 우리가 직접 제어 해야 한다.

 

하지만 get 메서드를 쓰지 않고, 다시 말해 unwrap 하지 않고 더하는 방법이 있다.

private Optional<Integer> optionalAdd(Optional<Integer> o1, Optional<Integer> o2) {
	return o1.flatMap(
			first -> o2.flatMap(
			second -> Optional.of(first + second)
			)
		);
}

위와 같이 만들면 우리가 직접 Optional context 를 다룰 필요가 없다.

cf) flatMap 메서드를 Monad 관점에서는 bind 라고 한다.

 

하지만 flatMap 에 익숙하지 않으면 위 코드를 바로 이해하기가 어렵다. flatMap 안에 flatMap 이 들어가면서 더 그렇다.

이제 Optional 과 비슷한 클래스를 직접 만들어 보면서 flatMap 을 좀 더 이해해보자.

 

Wrap 클래스를 만들고 Optional 과 같은 구조를 만들었다.

class Wrap<T> {
	private final T value;

	private Wrap(T value) {
		this.value = value;
	}

	public static <T> Wrap<T> of(T value) {
		return new Wrap<>(value);
	}
}

아래와 같이 of 메서드를 통해서 Wrap<Integer> 객체가 만들어지고 인자로 받은 값을 value로 채운다. 

Wrap<Integer> w1 = Wrap.of(1);
Wrap<Integer> w2 = Wrap.of(2);

 

다음은 map 메서드를 만들어보자.

public <R> Wrap<R> map(Function<T,R> mapper) {
	return Wrap.of(mapper.apply(value));
}

Function 을 파라미터로 받아서 수행한 결과를 Wrap 객체로 반환한다.

아래 코드를 보면 1이 든 w1 객체에 map 메서드를 호출할 때 Function 을 넘긴다. 이때 i 는 w1 의 1 이 들어가고 결과는 10이 된다. 체이닝으로 다시 map이 수행되고 같은 원리로 최종 결과는 110이 된다.

Wrap<Integer> w1 = Wrap.of(1);
Wrap<Integer> w2 = w1.map(i -> i + 9).map(i -> i * 11); // 110

 

그런데 아래와 같이 Function 을 만들면 결과가 Wrap<Wrap<Integer>> 타입이 된다. 하나씩 따라가보면 당연한 결과지만 이러한 형태를 원하는건 아니다. 

Wrap<Integer> w1 = Wrap.of(1);
Wrap<Wrap<Integer>> map = w1.map(i -> Wrap.of(i + 1));

 

이 문제를 해결하기 위해서 flatMap 이라는 메서드를 만들었다.

public <R> Wrap<R> flatMap(Function<T, Wrap<R>> mapper) {
	return mapper.apply(value);
}

두가지가 바꼈는데 하나는 Function<T,R> 에서 Function<T, Wrap<R>> 로 바꼈고, 다른 하나는 리턴할 때 Wrap.of 로 감싼것을 뺐다. 결과는 원하는 대로 Wrap<Integer> 가 나왔다.

Wrap<Integer> w1 = Wrap.of(1);
Wrap<Integer> flatMap = w1.flatMap(i -> Wrap.of(i + 1));

 

다시 아까 두개의 Optional 을 더하는 코드를 보자.

private Optional<Integer> optionalAdd(Optional<Integer> o1, Optional<Integer> o2) {
	return o1.flatMap(
			first -> o2.flatMap(
			second -> Optional.of(first + second)
			)
		);
}

first, second 는 Integer 타입이다. 좀 더 풀어서 보면 좀 더 이해가 편하다.

private Optional<Integer> optionalAdd(Optional<Integer> o1, Optional<Integer> o2) {
  return o1.flatMap(
    first -> {
      Optional<Integer> outer = o2.flatMap(
        second -> {
          Optional<Integer> inner = Optional.of(first + second);
          return inner;
        }
      );
      return outer;
    }
  );
}

만약 flatMap 이 아닌 map 을 사용하면 리턴 타입은 Optional<Optional<Optional<Integer>>> 이 된다.

 

 

 

 

참고 : www.youtube.com/watch?v=vePeILeSv4E

참고 : nazarii.bardiuk.com/posts/java-monad.html

참고 : medium.com/@afcastano/monads-for-java-developers-part-1-the-optional-monad-aa6e797b8a6e

참고 : medium.com/@afcastano/monads-for-java-developers-part-2-the-result-and-log-monads-a9ecc0f231bb

반응형

'Java' 카테고리의 다른 글

재귀 호출 최적화(Tail-Call)  (2) 2021.05.11
Singleton(면접단골질문)  (0) 2021.05.09
reflection 사용해서 api 문서화  (0) 2021.05.02
method return에 대한 고민  (2) 2021.02.07
오버로딩vs오버라이딩(면접단골질문)  (2) 2021.01.22