스폰서LobeHubLobeHub자세히 알아보기
dshfind

모나드: "상자"라는 디자인 패턴

결론부터 말씀드리니 마음 편히 가지셔도 됩니다. 모나드는 심오한 수학 객체가 아니라 소박한 프로그래밍 패턴입니다 — "값을 상자에 넣고, 창문으로만 꺼내 쓰도록 규정하는 것". 여러분은 이미 쓸 줄 압니다. 다만 그 이름을 몰랐을 뿐입니다.

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가 상자를 반환 → 그대로 반환 → 자연스럽게 "평탄화"
}

mapflatMap 안의 그 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 체인을 쓸 줄 알면 여러분은 이미 모나드를 쓰고 있는 것"이라고 말하는 이유입니다.

(可能为空 / 可能还没到)Just(x) / resolvemap / flatMap在盒子里操作规则:不能直接把值抠出来Promise 就是这种盒子

η 装值、μ 压平——盒子的规矩让副作用显式、可控、可组合

모나드가 이 논문과 무슨 관계인가?

한마디로 말하면: 모나드는 "함수형 언어가 부수 효과를 처리하는" 표준 도구입니다 — 부수 효과를 상자에 넣으면, 부수 효과가 명시적이고 제어 가능하며 조합 가능해집니다.

그리고 이 논문의 야심은 "부수 효과"를 "컴파일 타임 타입 시스템이 관리하는 정적 개념"에서 "런타임에 실제로 되돌릴 수 있는 동적 메커니즘"으로 격상시키는 것입니다(기억하시죠? 롤백 가능한 이펙트). 그래서 논문이 모나드를 언급하는 것은 "이펙트"라는 개념에 이론적 출처를 마련해 준 것일 뿐입니다 — 모나드 법칙을 유도할 필요는 없습니다. "모나드 = 부수 효과를 담는 상자"라는 것만 알면 충분합니다.

💡 직접 해 보세요: 저장소 루트 디렉터리의 monad-demo.js는 실행 가능한 데모 코드입니다. node monad-demo.js를 실행하면 위의 모든 예제가 실제로 동작하는 결과를 볼 수 있습니다.

자가 테스트 · 모나드

답을 모두 선택한 후 「답안 제출」을 클릭하면 정답 여부와 해설을 확인할 수 있습니다.

1. 모나드(Monad)를 가장 소박하게 이해한 것은 무엇인가요?
2. Maybe 박스(Just / Nothing)가 해결하는 핵심 문제는 무엇인가요?
3. 상자 모델에서 Promise.resolve(x)는 무엇에 해당하나요?
4. 논문이 모나드를 도입한 주된 목적은 무엇인가요?