오늘은 프로그래밍 세계에서 "공부 좀 한다" 하는 사람들도 머리를 싸매는 '모나드(Monad)'라는것을 학습할 계획입니다.

1. 일단 모나드란? '박스'를 이해하자!

코딩을 하다 보면 값을 그냥 두지 않고 상자(타입)에 넣을 때가 있어요.

  • List 상자: "값이 여러 개 들어있어!"
  • Optional 상자: "값이 있을 수도 있고, 없을 수도 있어!"

이 상자들을 다루는 두 가지 스타일, '상남자''실무자'가 있습니다.


2. 펑터 상남자 map 이라는 함수 스타일

상남자는 복잡한 거 딱 질색입니다.
상자 안에 뭐가 들었든 일단 자기가 하고 싶은 계산만 던집니다.

  • 특징: "난 알맹이만 변신시키면 끝이야! 상자가 어떻게 되든 내 알 바 아니지."
  • 문제점: 변신 결과가 또 '상자'라면? 상자 안에 상자가 들어가는 대참사가 발생합니다.
// 상남자의 최후: 상자가 중첩됨
val box = Optional.of("레고")

// 결과가 또 상자라면? (Optional 안에 Optional)
val result = box.map { item -> Optional.of(item + " 로봇") }

// 결과: Optional<Optional<"레고 로봇">> 
// 상자를 두 번 까야 알맹이가 나옵니다.  불편하는 현상입니다.

3. 프로 실무자 모나드 flatMap 스타일

flmap 이라는 함수는 꼼꼼합니다.
뒷사람이 고생하지 않게 상자를 까서 알맹이만 합쳐주는 작업(Flatten)까지 다 해줍니다.

  • 특징: "내가 좀 힘들더라도 결과물은 항상 깔끔하게 상자 하나로 합쳐줄게."
// 실무자의 깔끔한 정리
val box = Optional.of("레고")

// flatMap은 상자가 중복되면 하나를 까서 합쳐버립니다!
val result = box.flatMap { item -> Optional.of(item + " 로봇") }

// 결과: Optional<"레고 로봇"> 
// 깔끔하게 상자 하나! 바로 꺼내 쓰면 됩니다.

4. Reader 모나드와 프리 모나드의 고급 기술

자, 이제 질문하신 진짜 어려운 녀석들을 'flatMap'의 관점에서 코드로 만들게되면

① 리더 모나드는 "설정값을 배경에 깔아주마!" 라고합니다.

게임할 때 '난이도'나 '그래픽 설정'은 모든 함수에 일일이 전달하기 너무 귀찮기때문에?
리더 모나드는 이걸 배경처럼 깔아줍니다.

  • 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("하드모드"))) 
// 결과: 하드모드 -> 스하드모드 클리어!

② 일단 할 일 목록부터 적는 프리 모나드

로봇한테 명령할 때 바로 실행하면 나중에 수정이 안 됩니다.
프리 모나드는 일단 '할 일 목록'이라는 상자에 명령어를 담아둡니다.

  • flatMap의 비밀: 첫 번째 할 일을 적고, 그 아래에 두 번째 할 일을 꼬이지 않게 이어서 적어주는 역할을 합니다.
// 명령어 상자 로봇이 할 수 있는 일들
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("황금사과")] 
// 실행은 나중에 로봇이 이 종이를 보고 차례대로 하면 끝이 납니다.

5. 한눈에 비교하는 표

구분상남자 (map)프로 실무자 (flatMap)
태도"난 계산만 한다, 결과는 니가 알아서 해.""내가 뒷정리까지 다 한다."
결과상자 안에 상자가 생길 수 있습니다항상 깔끔한 상자 하나 비웁니다
핵심단순 변신연결과 책임
리더/프리사용하기 매우 힘듦배경을 전달하고, 목록을 이어줌

우리는 왜 flatMap을 써야 할까?

우리가 flatMap을 쓰는 이유는 "안전하고 깔끔한 기차 철로"를 만들기 위해서예요.
map은 철로를 뚝뚝 끊어놓지만, flatMap이라는 실무자는 앞 기차와 뒤 기차를 단단히 연결해 주거든요.

리더 모나드든 프리 모나드든 이름은 어렵지만, 결국 "실무자가 뒤처리를 잘해서 다음 단계로 깔끔하게 넘겨준다"는 것만 기억하면 된다는 것을 배우게 되었습니다.

profile
에러가 나도 괜찮아 — 그건 내가 배우고 있다는 증거야.

0개의 댓글