본 게시글은 모나드와 함수형 아키텍처 - 김성철 님의 게시글을 참고하여 정리하였습니다.

타입은 집합이다. boolean, int 같은 원시타입(Primitive Type) 외에도 구조체, 클래스, Enum 등도 모두 타입에 해당한다.
ex)
Boolean = {False, True}
Integer = {... -1, 0, 1, ...}
Double = {... 0.9, 0.99, 1.0 ...}
f : X → Y
함수는 두 집합을 연결하여 관계를 만들어주는 연산이다.
수학에서의 함수는 순수함수이다.
순수함수의 특징은 다음과 같다.
- 동일한 인자가 주어졌을 때, 항상 동일한 결과를 반환한다.
- 순수함수
f : X → Y는 집합 X의 원소를 함수 f에 대입하면 집합 Y의 원소가 나오는 성질이 항상 유지된다.
객체지향 프로그래밍의 메서드는 일반적으로 순수함수일까?
객체지향 세계에서 각 객체들은 상태(state)와 행위(behavior)를 가진다.
그리고 객체의 행위는 상태에 영향을 받는다.
아래 예제를 보자.
class MyClass {
int factor = 1;
public int calc(int val) {
return val + this.factor;
}
}
calc() 메서드의 반환값은 외부요소인 factor에 의존적이다. 만약 factor의 값이 변하면, 동일한 입력에 대해 다른 출력값을 반환한다.
따라서 이는 순수함수가 아니다.
public int div(int a, int b) {
return a / b;
}
그렇다면 위와 같은 div() 메서드는 어떨까?
의존하는 외부요소가 없으므로 순수함수라 생각할 수 있다.
하지만 만약 b가 0이라면,
java.lang.ArithmeticException 이 발생한다.
int 집합 → int 집합 의 관계가 항상 보장되지 않으므로 순수함수가 아닌 것이다.
함수 외부 요소에 의존하지 않더라도, 결과 집합 이외의 집합값을 발생시키면 순수함수가 아니다.

함수
f : X → Y, g : Y → Z 일 때,
두 함수의 합성은
g ∘ f : X → Z 이다.
프로그래밍 세계에서도 합성이 존재한다.
예를 들어 인터넷에서 사이트를 옮겨 다니게 하는 링크는 함수이다.
링크를 계속 클릭하여 웹을 탐방하는 것은 함수의 합성으로 생각할 수 있다.
link : site → site
web : link ∘ link ∘ link ...∘ link
함수의 합성 덕분에 우리는 커다란 문제를 작은 문제들로 쪼개어 풀 수 있다.
하지만 그것이 항상 성공하지는 않는다. 왜냐하면, 사이드이펙트(Side Effect) 때문이다.
사이드이펙트는 어떤 함수가 존재할 때, 이 함수가 순수함수가 될 수 없게 만드는 모든 것을 의미한다.
사이드이펙트가 존재하여 순수함수성이 깨지면, 함수의 합성은 더 이상 진행될 수 없다.
- 우리가 작성한 프로그램은 커다란 문제를 작은 문제들로 쪼개어 해결하는 함수의 합성이다.
- 사이드 이펙트가 존재하면 함수의 합성이 실패할 수 있다.

Monad는 일종의 디자인 패턴으로써, 결과값 집합과 오류값 집합을 하나의 집합으로 만든 것이다.
함수의 실행 결과를 모나드로 반환한다면, 순수함수의 성질을 잃지 않을 수 있다.
순수함수의 성질을 잃지 않기 때문에, 함수의 합성을 지속적으로 이어나갈 수 있다.
간단한 모나드의 예제를 보자.
public abstract class Result<T> {
public static class Success<T> extends Result<T> {
T value;
public Success(T value) {
this.value = value;
}
}
public static class Fail<T> extends Result<T> {
}
}
Result<T> 모나드를 도입하고 {Success, Fail} 집합을 포함한다.
함수 내부 연산에서 사이드 이펙트가 발생하면, Fail을 반환한다.
public int div(int a, int b) {
return a / b;
} // b가 0일 때 사이드 이펙트가 발생한다.
public Result<Integer> div(int a, int b) {
try {
return new Result.Success<>(a / b);
} catch (Throwable e) {
return new Result.Fail<>();
}
}
div()의 반환값을 모나드로 변경한다.
var a = div(10, 0);
if(a instanceof Result.Success<Integer>) {
//...
} else if(a instanceof Result.Fail<Integer>) {
//...
}
위와 같이 사용한다면, div의 두 번째 매개변수로 0이 입력되더라도, 에러가 발생하지 않는다.
모나드에는 Map과 FlatMap 기능이 존재한다.

Map은 구체타입 -> 구체타입으로 변환하는 함수를 입력받고, 모나드를 반환한다.
FlatMap은 구체타입 -> 모나드로 변환하는 함수를 입력받고, 모나드를 반환한다.
Optional<Integer> len1 = Optional.of("Hello")
.map(s -> s.length());
Optional<Integer> len2 = Optional.of("Hello")
.flatMap(s -> Optional.of(s.length()));
위와 같이 map() 에는 구체타입을 반환하는 람다식이,
flatMap()에는 Optional<>을 반환하는 람다식이 들어가는 것을 알 수 있다.
두 함수의 반환값은 모두 Optional<>로 모나드를 반환한다.
즉 map, flatMap을 사용해서 메서드 체이닝이 가능하며, 모나드 패턴을 적용하여 체이닝의 중간 단계에서 실패하지 않는다.

Optional.map()의 구현 코드를 보면 값이 존재하지 않을 때, mapper 함수를 적용하지 않고, Optional.empty()를 반환하는 것을 볼 수 있다.