3P by bboydart91 3시간전 | ★ favorite | 댓글 1개

본문

  • 펑터의 map만으로는 해결할 수 없는 두 가지 문제(컨테이너 안 함수가 갇히는 문제, 합성 시 맥락 중첩 문제)에서 출발하여 어플리케이티브 펑터와 모나드까지 도달하는 과정을 TypeScript 코드로 설명
  • 1988년 에우제니오 모지(Eugenio Moggi)가 프로그램을 A → B가 아닌 A → T(B)로 모델링한 배경에서 시작
  • flatMap = map + join이라는 구조와, 이를 안전하게 사용하기 위한 세 가지 법칙(결합, 좌단위, 우단위)을 다룸
  • 모나드가 왜 "내부함자 범주의 모노이드 대상" 인지를 정수 덧셈의 모노이드에 대비하여 설명
  • Promise는 모나딕하게 작동하지만 엄밀한 수학적 모나드는 아닌 이유도 언급

펑터의 한계: map으로 안 되는 것들

  • 커링된 함수를 map으로 적용하면 결과가 Maybe<(b: number) => number>처럼 함수가 컨테이너 안에 갇힘
    • map은 컨테이너 바깥의 함수만 받을 수 있어서, 안에 갇힌 함수를 다른 값에 적용할 방법이 없음
  • 펑터를 반환하는 두 함수를 합성하면 Maybe<Maybe>처럼 맥락이 중첩됨
    • 단계가 늘어날수록 Maybe<Maybe<Maybe<...>>>로 무한 중첩

어플리케이티브 펑터: 컨테이너 안의 함수 적용

  • apply 연산으로 컨테이너 안에 갇힌 함수를 다른 컨테이너의 값에 적용 가능
    • apply: T<(A → B)> → T<A> → T<B>
  • pure 연산으로 순수 값을 컨테이너에 삽입
  • 한계: 어떤 컨테이너들을 합성할지 미리 정해져 있어야 함
    • 이전 계산 결과를 보고 다음 계산을 결정하는 동적 순차 의존성을 표현할 수 없음

모나드: 중첩을 펴는 연산의 발명

  • join 연산이 T<T<A>> → T<A>로 이중 컨테이너를 단일로 펴줌
    • JavaScript의 Array.prototype.flat이 같은 역할
  • 실무에서는 map + join을 합친 flatMap을 사용
    • flatMap: T<A> → (A → T<B>) → T<B>
    • map은 A → B를 받지만 flatMap은 A → T<B>를 받아서 결과를 한 겹으로 유지

flatMap의 세 가지 법칙

  • 결합 법칙: 삼중 중첩 T(T(T(A)))를 펼 때 안쪽부터 펴든
    바깥부터 펴든 결과가 동일해야 함
    • m.flatMap(f).flatMap(g) === m.flatMap(x =>
      f(x).flatMap(g))
  • 좌단위 법칙: pure로 넣었다가 바로 flatMap하면 그냥 함수를 직접 적용한 것과 동일
    • pure(a).flatMap(f) === f(a)
  • 우단위 법칙: flatMap에 pure를 넘기면 원래 컨테이너 그대로
    • m.flatMap(pure) === m

"내부함자 범주의 모노이드 대상" 분해

  • 프로그래밍의 펑터는 타입 세계에서 타입 세계로 향하므로 엔도펑터(내부함자)
  • 엔도펑터들 자체를 대상으로 놓는 엔도펑터 카테고리를 구성할 수 있음
  • 모노이드의 요건(이항 연산 + 결합 법칙 + 항등원)에 대입하면:
    • 이항 연산 = join
    • 항등원 = pure
    • 정수 덧셈의 모노이드와 구조가 정확히 대응됨

Promise는 모나드가 아닌 이유

  • then이 map과 flatMap을 반환값에 따라 섞어서 처리함
  • Promise<Promise> 상태가 런타임에서 허용되지 않고 즉시 단일
    계층으로 합쳐짐
  • 실무적으로 편리하지만 수학적 모나드 법칙을 만족하지 않음

Comonad도 다뤄주세요!