Monad
모나드의 개념과 활용을 Functor를 기반으로 설명하며, 비동기 처리 및 일반적인 모델링이 어려운 상황을 해결하는 방법을 알아봅니다.
- 들어가기 전에 DB 에 데이터를 저장하는 예시를 들어 보면, IO 과정에서 비동기 처리가 이루어지게 된다. 이러한 Process 가 Blocking 형태로 처리되게 되면 이 작업은 한 쓰레드 전체를 점유하게 된다. Non-Blocking 형식으로 처리하는 방법으로는 여러가지가 있지만, Javascript 의 경우에는 Callback 함수, Promise, Async 방식을 이용한다. 이 예시에서, Javascript 의 Promise 는 Monad 이다. Java 의 Optional Class 또한 Monad 의 일종이다.
 
Optional.ofNullable(response.getCart()).ifPresent(cart -> {
    Optional.ofNullable(c.getProduct()).ifPresent(p -> 
        System.out.println(p.getName()));
})
- Monad 란 무엇인가 ?
 
Monad는 값을 담는 컨테이너의 일종이다. Functor 를 기반으로 구현되어있다. flatMap() 메소드를 제공한다. Monad Laws 를 만족시키는 구현체를 말한다.
- Functor 은 무엇인가?
 
import java.util.function.Function;
interface Functor<T> {
    <R> Functor<R> map(Function<T, R> f);
}
map() 의 진정한 의미는 ~~컬렉션의 원소를 순회하는 방법~~ T 타입의 Functor 를 R 타입의 Functor 로 바꾸는 것이다.
List<String> numberStrings = Arrays.asList("1", "2", "3", "4", "5");
List<Integer> numbers = numberStrings.stream()
                                        // <String> Functor -> <Integer> Functor
                                        .map(Integer::parseInt)
                                        .collect(Collectors.toList())
- Functor 은 왜 쓸까 ?
 
- Functor 를 이용하면, 일반적으로 모델링할 수 없는 상황을 모델링 할 수 있다. (Ex. 값이 없거나, 미래에 값이 있는 케이스)
 
class FOptional<T> implements Functor<T, FOptional<?>> {
    private final T valueOrNull;
    private FOptional(T valueOrNull) {
        this.valueOrNull = valueOrNull;
    }
    public <R> FOptional<R> map(Function<T, R> f) {
        if(valueOrNull == null) {
            return empty();
        }
        else {
            return of(f.apply(valueOrNull));
        }
    }
    public static <T> FOptional<T> of(T t) {
        return new FOptional<T>(t);
    }
    public static <T> FOptional<T> empty() {
        return new FOptional<T>(null);
    }
}
...
// null 값이지만, 모나드를 이용해서 합성 함수를 만들 수 있다.
// -> 사용하는 쪽에서 null 체크가 불필요 
// (타입안정성을 지키면서 null 인코딩 가능)
FOptional<String> optionStr = FOptional(null);
FOptional<Integer> optionInt = optionStr.map(Integer::parseInt);
- 함수를 합성할 수 있다. (비동기 연산들도 합성할 수 있다.)
 
// sudo code 
Promise<Customer> customer = ...;
Promise<byte[]> bytes = customer.map(Customer::getAddress) // return Promise<Address>
                                .map(Address::street) // return Promise<String>
                                .map((String s) -> s.substring(0, 3)) // return Promise<String>
                                .map(String::toLowerCase) // return Promise<String>
                                .map(String::getBytes); // return Promise<byte[]>
- List 는 무엇일까 ? 코드를 보는것이 더 이해가 빠를 수 있어서, 코드로 정리했다.
 
class FList<T> implements Functor<T, FList<?>> {
    // 단순히 Functor 가 담고 있는 값이 list 임.
    private final ImmutableList<T> list; 
    FList(Iterable<T> value) {
        this.list = ImmutableList.copyOf(value);
    }
    @Override
    public <R> FList<?> map(Function<T, R> f) {
        ArrayList<R> result = new ArrayList<R>(list.size());
        for(T t : list) {
            // list 의 모든 원소에 함수 f를 적용
            result.add(f.apply(t));
        }
        return new FList<>(result);
    }
}
- 그럼 Monad 는 뭘까 ?
 
Functor 에 flatMap() 이 추가된 형태이다.
FOptional<String> num = FOptional.of("42");
FOptional<Integer> answer = num.map(this::tryParse); // num 이 FOptional<String> 이어서 chaining 불가능
때로는 Functor 자체를 반환하는 함수도 많은데, Functor\<Functor\<T>> 의 구조가 되면, 정상적인 함수 chaning 이 불가능해진다. Functor 를 반환하는 함수는 매우 일반적이다. 그래서 FlatMap 이 정의된다.
interface Monad<T, M extends Monad<?, ?>> extends Functor<T, M> {
    M flatMap(Function<T, M> f);
}
FOptional<String> num = FOptional.of("42");
FOptional<Integer> answer = num.flatMap(this::tryParse); // num 은 FOptional<String>
FOptional<Date> date = answer.map(t -> new Date(t)); // 합성 가능
여기서, 위에서 설명했던 Monad 의 의미를 다시 읽어보면 조금 더 이해가 잘될 수 있다.
Monad는 값을 담는 컨테이너의 일종이다. Functor 를 기반으로 구현되어있다. flatMap() 메소드를 제공한다. Monad Laws 를 만족시키는 구현체를 말한다.
- Monad 의 의의
 
- 값이 없는 상황이나, 값이 미래에 이용 가능해질 상황 등, 일반적으로는 할 수 없는 여러 상황을 모델링 할 수 있음.
 - 비동기 로직을 동기 로직으로 구현하는 것과 동일한 형태로 구현하면서도, 함수의 합성 및 완전한 non-blocking pipeline을 구현할 수 있음.
 
이것도 읽어보세요