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 |