모나드: "상자"라는 디자인 패턴
결론부터 말씀드리니 마음 편히 가지셔도 됩니다. 모나드는 심오한 수학 객체가 아니라 소박한 프로그래밍 패턴입니다 — "값을 상자에 넣고, 창문으로만 꺼내 쓰도록 규정하는 것". 여러분은 이미 쓸 줄 압니다. 다만 그 이름을 몰랐을 뿐입니다.
1단계: 왜 "상자"인가?
먼저 여러분이 매일 겪는 고충을 살펴 보겠습니다 — null 체크 지옥:
function 查朋友(id) {
const user = users[id];
if (user === undefined) return '查无此人'; // ① null 체크
const friend = friends[user];
if (friend === undefined) return '这人没朋友'; // ② 또 null 체크
return friend;
}
"실패할 수 있는" 작업을 하나 추가할 때마다 if를 하나 더 추가해야 합니다. 코드가 길어질수록 null 체크가 늘어나고, 하나라도 빠뜨리면 터집니다. 상자(모나드)는 바로 이 if들을 없애기 위해 존재합니다.
2단계: 가장 간단한 상자 만들기 — Maybe 박스
이 상자에는 두 가지 상태만 있습니다. 값이 들어 있거나, 비어 있거나입니다.
function Just(value) { return { type: 'just', value }; } // 값 있음
function Nothing() { return { type: 'nothing' }; } // 비어 있음
정말 이것뿐입니다 — 객체 하나에 표시 하나, 신비로운 것은 전혀 없습니다.
3단계: 상자의 규칙 — 값을 직접 꺼내는 것 금지
상자의 가장 핵심적인 설계: 안에 있는 값을 직접 꺼낼 수 없습니다. 안의 값을 쓰려면 오직 "창문"을 통해서만 가능합니다:
// 창문 1: map —— 상자에 값이 있으면 f로 처리해 다시 상자에 넣고, 없으면 그대로 흘려보냄
function map(box, f) {
if (box.type === 'nothing') return Nothing();
return Just(f(box.value));
}
// 창문 2: flatMap —— 모나드의 영혼! f는 "새로운 상자"를 반환함
function flatMap(box, f) {
if (box.type === 'nothing') return Nothing();
return f(box.value); // f가 상자를 반환 → 그대로 반환 → 자연스럽게 "평탄화"
}
map과 flatMap 안의 그 null 체크 한 줄을 주목해 보세요 — null 체크 로직이 창문 안에 작성되어 있으므로, 여러분은 더 이상 null 체크를 손으로 쓸 필요가 없습니다!
개념 대응:
Just(x)는 논문에서 말하는 η (eta) — "순수한 값을 상자에 넣기"입니다.flatMap은 논문에서 말하는 **μ (mu)**와 bind — "상자 속의 상자를 평탄화하기"입니다.
4단계: 이것으로 null 체크 지옥 해결하기
"사용자 조회 → 친구 조회" 시나리오로 돌아가서, 이제 각 단계가 상자를 반환합니다:
function findUser(id) { return users[id] ? Just(users[id]) : Nothing(); }
function findFriend(u) { return friends[u] ? Just(friends[u]) : Nothing(); }
// 체이닝 호출: if가 0개!
function getFriendOf(id) {
return flatMap(findUser(id), findFriend);
}
실행 결과:
| 호출 | 결과 | 의미 |
|---|---|---|
getFriendOf(1) | Just('小刚') | 정상적으로 찾음 |
getFriendOf(999) | Nothing | 사용자를 찾을 수 없음, 빈 상자가 자동으로 아래로 전달되어 터지지 않음 |
getFriendOf(2) | Just('小丽') | 정상적으로 찾음 |
무엇이 신기할까요? 사용자가 존재하지 않으면 findUser는 빈 상자를 반환합니다. flatMap은 비어 있는 것을 보고 findFriend를 호출하지 않고 빈 상자를 그대로 전달합니다. null 체크 로직을 상자가 자동으로 떠맡아 줍니다 — 빠뜨리고 싶어도 빠뜨릴 수가 없습니다. 게다가 몇 겹이든 연결할 수 있으며, 코드 구조는 항상 똑같습니다:
const deep = flatMap(flatMap(findUser(1), findFriend), findFriend2);
// → Just('大壮')
5단계: 깜짝 공개 — 여러분은 매일 모나드를 쓰고 있습니다, 그 이름은 Promise
| 상자 (Maybe) | Promise | 둘 다 "상자" |
|---|---|---|
Just(5) | Promise.resolve(5) | 일반 값을 상자에 넣기 |
map(box, f) | promise.then(f) | 상자 안의 값을 변환 |
flatMap(box, f) | promise.then(f)(f가 Promise를 반환) | 상자 속의 상자, 자동으로 평탄화 |
| 빈 상자가 아래로 전달됨 | rejected Promise가 아래로 전달됨 | 실패가 자동으로 전파, 터지지 않음 |
box.value(금지) | await(언어 차원에서만 허용) | 상자를 여는 유일한 방법 |
유일한 차이는 상자에 "딸려 있는 것"이 다르다는 점입니다:
- Maybe 박스에 딸려 있는 것: "이 값은 존재하지 않을 수 있다"
- Promise 박스에 딸려 있는 것: "이 값은 조금 기다려야 하고, 실패할 수도 있다"
상자 자체(디자인 패턴)는 완전히 똑같습니다. 이것이 ".then 체인을 쓸 줄 알면 여러분은 이미 모나드를 쓰고 있는 것"이라고 말하는 이유입니다.
η 装值、μ 压平——盒子的规矩让副作用显式、可控、可组合
모나드가 이 논문과 무슨 관계인가?
한마디로 말하면: 모나드는 "함수형 언어가 부수 효과를 처리하는" 표준 도구입니다 — 부수 효과를 상자에 넣으면, 부수 효과가 명시적이고 제어 가능하며 조합 가능해집니다.
그리고 이 논문의 야심은 "부수 효과"를 "컴파일 타임 타입 시스템이 관리하는 정적 개념"에서 "런타임에 실제로 되돌릴 수 있는 동적 메커니즘"으로 격상시키는 것입니다(기억하시죠? 롤백 가능한 이펙트). 그래서 논문이 모나드를 언급하는 것은 "이펙트"라는 개념에 이론적 출처를 마련해 준 것일 뿐입니다 — 모나드 법칙을 유도할 필요는 없습니다. "모나드 = 부수 효과를 담는 상자"라는 것만 알면 충분합니다.
💡 직접 해 보세요: 저장소 루트 디렉터리의
monad-demo.js는 실행 가능한 데모 코드입니다.node monad-demo.js를 실행하면 위의 모든 예제가 실제로 동작하는 결과를 볼 수 있습니다.
자가 테스트 · 모나드
답을 모두 선택한 후 「답안 제출」을 클릭하면 정답 여부와 해설을 확인할 수 있습니다.
