
오늘은 프로그래밍 세계에서 "공부 좀 한다" 하는 사람들도 머리를 싸매는 '모나드(Monad)'라는것을 학습할 계획입니다.
코딩을 하다 보면 값을 그냥 두지 않고 상자(타입)에 넣을 때가 있어요.
이 상자들을 다루는 두 가지 스타일, '상남자'와 '실무자'가 있습니다.
상남자는 복잡한 거 딱 질색입니다.
상자 안에 뭐가 들었든 일단 자기가 하고 싶은 계산만 던집니다.
// 상남자의 최후: 상자가 중첩됨
val box = Optional.of("레고")
// 결과가 또 상자라면? (Optional 안에 Optional)
val result = box.map { item -> Optional.of(item + " 로봇") }
// 결과: Optional<Optional<"레고 로봇">>
// 상자를 두 번 까야 알맹이가 나옵니다. 불편하는 현상입니다.
flmap 이라는 함수는 꼼꼼합니다.
뒷사람이 고생하지 않게 상자를 까서 알맹이만 합쳐주는 작업(Flatten)까지 다 해줍니다.
// 실무자의 깔끔한 정리
val box = Optional.of("레고")
// flatMap은 상자가 중복되면 하나를 까서 합쳐버립니다!
val result = box.flatMap { item -> Optional.of(item + " 로봇") }
// 결과: Optional<"레고 로봇">
// 깔끔하게 상자 하나! 바로 꺼내 쓰면 됩니다.
자, 이제 질문하신 진짜 어려운 녀석들을 'flatMap'의 관점에서 코드로 만들게되면
게임할 때 '난이도'나 '그래픽 설정'은 모든 함수에 일일이 전달하기 너무 귀찮기때문에?
리더 모나드는 이걸 배경처럼 깔아줍니다.
// 설정값을 나타내는 클래스
data class GameConfig(val difficulty: String)
// Reader는 "설정값을 주면 값을 만들어줄게"라는 약속이에요.
class Reader<T>(val run: (GameConfig) -> T) {
fun <R> flatMap(next: (T) -> Reader<R>): Reader<R> {
return Reader { config ->
val result = this.run(config) // 1. 현재 설정으로 값을 만들고
next(result).run(config) // 2. 그 결과를 다음 실무자에게 설정값과 함께 넘겨줌!
}
}
}
// 사용 예시
val stage1 = Reader { config -> "스테이지 1 (${config.difficulty})" }
val finalGame = stage1.flatMap { info ->
Reader { config -> "$info -> 스테이지 2 (${config.difficulty}) 클리어!" }
}
println(finalGame.run(GameConfig("하드모드")))
// 결과: 하드모드 -> 스하드모드 클리어!
로봇한테 명령할 때 바로 실행하면 나중에 수정이 안 됩니다.
프리 모나드는 일단 '할 일 목록'이라는 상자에 명령어를 담아둡니다.
// 명령어 상자 로봇이 할 수 있는 일들
sealed class RobotCmd<out T>
data class MoveForward(val distance: Int) : RobotCmd<Unit>()
data class PickUp(val item: String) : RobotCmd<Unit>()
// Free는 "명령어들을 순서대로 적은 종이"예요.
class Free<T>(val instructions: List<RobotCmd<*>>) {
fun flatMap(next: (T) -> Free<T>): Free<T> {
// 기존 목록에 다음 명령어를 깔끔하게 덧붙입니다
// 여기서는 이해를 돕기 위해 간단히 리스트를 합치는 걸로 보여주게됩니다
return Free(this.instructions + next(Unit as T).instructions)
}
}
// 사용 예시
val start = Free<Unit>(listOf(MoveForward(10)))
val plan = start.flatMap {
Free<Unit>(listOf(PickUp("황금사과")))
}
// 결과: [MoveForward(10), PickUp("황금사과")]
// 실행은 나중에 로봇이 이 종이를 보고 차례대로 하면 끝이 납니다.
| 구분 | 상남자 (map) | 프로 실무자 (flatMap) |
|---|---|---|
| 태도 | "난 계산만 한다, 결과는 니가 알아서 해." | "내가 뒷정리까지 다 한다." |
| 결과 | 상자 안에 상자가 생길 수 있습니다 | 항상 깔끔한 상자 하나 비웁니다 |
| 핵심 | 단순 변신 | 연결과 책임 |
| 리더/프리 | 사용하기 매우 힘듦 | 배경을 전달하고, 목록을 이어줌 |
우리가 flatMap을 쓰는 이유는 "안전하고 깔끔한 기차 철로"를 만들기 위해서예요.
map은 철로를 뚝뚝 끊어놓지만, flatMap이라는 실무자는 앞 기차와 뒤 기차를 단단히 연결해 주거든요.
리더 모나드든 프리 모나드든 이름은 어렵지만, 결국 "실무자가 뒤처리를 잘해서 다음 단계로 깔끔하게 넘겨준다"는 것만 기억하면 된다는 것을 배우게 되었습니다.