모나드 & 컴프리헨션 블록
모나드(Monad)와 컴프리헨션 블록(Comprehension Block)은 함수형 프로그래밍에서 가장 중요하면서도, 동시에 가장 악명 높게 헷갈리는 개념들입니다. 결론부터 말씀드리면, 모나드는 데이터를 다루는 '규칙을 가진 상자'이고, 컴프리헨션 블록은 이 상자들을 '아주 쉽게 열고 닫게 해주는 마법의 문법(Syntactic Sugar)'입니다. 두 개념이 어떻게 연결되는지 구체적으로 쪼개서 설명해 드리겠습니다.
1. 모나드 (Monad)란 무엇인가?
수학적인 정의(범주론)는 잠시 잊으셔도 좋습니다. 프로그래밍에서 모나드는 '문맥(Context)을 가진 상자'이자 일종의 디자인 패턴입니다. 어떤 값이 상자(모나드) 안에 들어있다면, 우리는 그 값을 직접 꺼내서 조작하는 대신 "상자야, 네 안의 값에 이 함수를 적용해서 다시 상자에 담아줘"라고 명령해야 합니다.
모나드의 2가지 핵심 연산
모나드가 되려면 다음 두 가지 기능이 반드시 있어야 합니다.
-
상자에 넣기
(
return,pure, 생성자) — 일반적인 값을 상자 안에 넣는 기능입니다. 예:x ➔ Box(x) -
연결하기
(
bind,flatMap,>>=) — 상자 안의 값을 꺼내서, 새로운 상자를 반환하는 함수에 통과시킨 뒤, 결과가 든 상자를 반환합니다. 이flatMap덕분에 상자가 중첩되는 것(예:Box(Box(x)))을 막고 평평한Box(x)로 유지할 수 있습니다.
왜 모나드를 쓸까요?
값이 없을 수도 있는 상황(Option/Maybe), 비동기
처리(Promise/Future), 여러 개의
값(List) 등을 다룰 때 발생하는 부수 효과(Side Effect)나 에러
처리를 상자 자체가 알아서 처리하게 만들기 위해서입니다. 개발자는 핵심
로직에만 집중할 수 있습니다.
2. 컴프리헨션 블록 (Comprehension Block)이란?
컴프리헨션은 리스트나 모나드 같은 컬렉션을 선언적이고 읽기 쉽게 조작하고 생성하는 문법입니다. 파이썬(Python)의 리스트 컴프리헨션이 가장 유명한 예시입니다.
# 일반적인 반복문
results = []
for x in range(5):
if x > 0:
results.append(x * 2)
# 컴프리헨션 블록
results = [x * 2 for x in range(5) if x > 0]
코드가 훨씬 간결해지고 "무엇(What)을 만들 것인가"에 집중하게 해줍니다. 하지만 컴프리헨션의 진가는 단순한 리스트를 넘어 모나드와 결합할 때 나타납니다.
3. 모나드와 컴프리헨션의 관계 (핵심)
컴프리헨션 블록(스칼라의 for 컴프리헨션, 하스켈의
do 표기법 등)은 사실 모나드의 flatMap과
map 연산을 보기 좋게 포장한 껍데기(Syntactic Sugar)입니다.
모나드를 연속해서 사용할 때 컴프리헨션이 없다면, 코드는 콜백
지옥(Callback Hell)처럼 끝없이 파고들게 됩니다. 스칼라(Scala) 언어의
예시를 통해 비교해 보겠습니다. 사용자(User)를 찾고, 그 사용자의
주문(Order)을 찾는 연속된 과정을 가정해 봅시다. (데이터가 없을 수도
있으므로 Option 모나드를 사용합니다.)
❌ 모나드 메서드만 사용했을 때 (읽기 어려움):
// flatMap과 map이 중첩되면서 코드가 오른쪽으로 계속 밀려납니다.
val result = findUser(1).flatMap(user =>
findOrder(user.id).map(order =>
s"${user.name}님의 주문: ${order.item}"
)
)
🟢 컴프리헨션 블록을 사용했을 때 (매우 직관적임):
// for 컴프리헨션 블록 사용
val result = for {
user <- findUser(1) // 상자에서 user를 꺼냄 (실제로는 flatMap)
order <- findOrder(user.id) // 상자에서 order를 꺼냄 (실제로는 flatMap/map)
} yield s"${user.name}님의 주문: ${order.item}"
컴파일러는 아래의 for 컴프리헨션 블록을 위쪽의
flatMap / map 체인으로 자동으로 변환합니다.
즉, 컴프리헨션 블록 안에서 <- 기호를 써서 변수를
뽑아내는 행위 자체가, 사실은 모나드의 상자를 열고 체인(Chain)을 걸어주는
수학적 연산을 수행하고 있는 것입니다.
요약하자면
- 모나드는 안전한 데이터 체이닝을 위해
flatMap같은 규칙을 정의한 설계 도면(인터페이스)입니다. - 컴프리헨션 블록은 그 복잡한
flatMap체이닝을 일반적인 동기식 프로그래밍처럼 직관적이고 우아하게 작성하도록 도와주는 문법입니다.
for
컴프리헨션도 do 표기법도 없습니다 — 바로 그래서 FxDart의
either((r) { ... }) 블록이
존재합니다. 컴프리헨션 블록과 같은 역할(flatMap 피라미드
대신 일직선 코드)을 하지만, 모나드 디슈거링이 아니라
Raise 스코프를 통해 동작합니다 — 이 구분이 왜 중요한지는
이름에 담긴 이유를 보세요.