← 목록으로
범주론 · 개론

자연수의 분배 법칙을 손쉽게 확장하는 법

a(b+c) = ab + ac, proved once — and for all worlds

초등학교에서 배우는 \(a(b+c)=ab+ac\). 이 뻔한 식을 범주론으로 증명해 보면, 신기하게도 그 증명이 자연수를 떠나 기수·벡터공간·위상공간·아벨군, 나아가 프로그래밍 언어의 타입에까지 그대로 통합니다. 에밀리 릴(Emily Riehl)의 MAA MathFest 발표를 따라가는, 범주론 입문기.

0뻔한 식을 왜 "증명"하나

\(a(b+c)=ab+ac\)는 의심할 여지가 없습니다. 그런데 수학자가 굳이 이걸 증명하는 이유는 두 가지예요. 첫째, 증명하는 과정이 범주론의 핵심 도구들 — 보편 성질, 요네다 보조정리, 수반(adjunction) — 을 가장 작은 무대에서 보여 줍니다. 둘째, 그렇게 얻은 증명은 자연수에만 매이지 않습니다. "곱"과 "합"이 무엇인지를 관계로만 규정해 두면, 똑같은 논증이 전혀 다른 세계에서도 한 글자 안 고치고 작동하죠.

요점은 이렇습니다 — 대상이 무엇으로 "만들어졌는지"는 잊고, 그것이 다른 대상들과 어떤 화살표로 이어지는지만 본다. 이 한 걸음이 범주론의 전부이자, 분배법칙을 보편적으로 만드는 비결입니다.


1수를 집합으로 바꾸기 — 범주화

첫 단계는 범주화(categorification)입니다. 자연수 \(a\)를, 원소가 \(a\)개인 유한집합 \(A\)로 바꿔 생각하죠 (\(a=|A|\)). 그러면:

그러면 분배법칙은 수의 등식이 아니라, 두 집합 사이의 자연스러운 일대일대응(전단사)이 됩니다:

$$ A\times(B+C)\;\cong\;(A\times B)+(A\times C). $$

왼쪽은 "\(A\)의 원소 하나, 그리고 \(B\) 또는 \(C\)의 원소 하나"인 쌍, 오른쪽은 "\(A\!\times\!B\)의 쌍 또는 \(A\!\times\!C\)의 쌍". 같은 데이터의 두 표현일 뿐이죠. 아래 격자가 그 대응을 보여 줍니다.

분배법칙은 격자를 두 색으로 나누는 일

a = 3
b = 2
c = 3
\(a\)열 \(\times(b+c)\)행 격자의 점 개수가 \(a(b+c)\). 위 \(b\)행(파랑 \(=A\times B\))과 아래 \(c\)행(주황 \(=A\times C\))으로 갈리니, \(a(b+c)=ab+ac\)가 곧 점을 두 색으로 나누는 일입니다.

2보편 성질 — "대상은 화살표로 안다"

그 전단사를 손으로 만들 수도 있지만, 범주론은 더 영리하게 굽니다. 곱과 쌍대곱을 "무엇으로 만들었는가"가 아니라 "어떤 화살표를 받아들이는가"로 규정하는 것 — 이것이 보편 성질(universal property)입니다.

곱의 보편 성질 (pairing)
임의의 대상 \(X\)에서 \(A\times B\)로 가는 화살표는, \(X\to A\)와 \(X\to B\) 한 쌍과 정확히 같다: $$\operatorname{Hom}(X,\,A\times B)\;\cong\;\operatorname{Hom}(X,A)\times\operatorname{Hom}(X,B).$$
쌍대곱의 보편 성질 (copairing)
\(B+C\)에서 임의의 대상 \(X\)로 가는 화살표는, \(B\to X\)와 \(C\to X\) 한 쌍과 정확히 같다: $$\operatorname{Hom}(B+C,\,X)\;\cong\;\operatorname{Hom}(B,X)\times\operatorname{Hom}(C,X).$$

여기서 \(\operatorname{Hom}(X,Y)\)는 \(X\)에서 \(Y\)로 가는 모든 화살표(함수)의 집합입니다. 핵심 철학은 이렇습니다 — 한 대상의 정체는, 그 대상과 주고받는 화살표들의 무늬에 전부 담겨 있다. 곱은 "둘로 쪼개 받는 입구", 쌍대곱은 "둘로 쪼개 내보내는 출구"로 정의되죠. 이 관점을 끝까지 밀어붙인 것이 다음의 요네다 보조정리입니다.


3요네다 보조정리

각 대상 \(A\)에, "그 대상으로 들어오는 화살표를 모으는 함자" \(h_A=\operatorname{Hom}(-,A)\)를 붙입니다. \(h_A\)는 임의의 대상 \(X\)에 집합 \(\operatorname{Hom}(X,A)\)를 대응시키죠. 이런 \(h_A\) 꼴의 함자를 표현가능 함자(representable functor)라 부릅니다.

요네다 보조정리 (Yoneda lemma)
함자 \(F\)와 대상 \(A\)에 대해, \(h_A\)에서 \(F\)로 가는 자연변환 전체는 집합 \(F(A)\)와 자연스럽게 일대일대응한다: $$\operatorname{Nat}(h_A,\,F)\;\cong\;F(A).$$

그 즉각적인 따름정리가 우리에게 필요한 무기입니다. \(F\)를 또 다른 표현가능 함자 \(h_B\)로 두면

$$\operatorname{Hom}(A,B)\;\cong\;\operatorname{Nat}(h_A,\,h_B),$$

대상 사이의 화살표 = 그들이 표현하는 함자 사이의 자연변환입니다(요네다 매장은 충실충만합니다). 특히:

우리가 쓸 한 줄
두 대상 \(Y,Z\)가 동형(\(Y\cong Z\))일 필요충분조건은, 모든 \(X\)에 대해 \(\operatorname{Hom}(Y,X)\cong\operatorname{Hom}(Z,X)\)가 \(X\)에 대해 자연스럽게 성립하는 것. 바꿔 말해 — 두 대상이 똑같은 화살표 무늬를 가지면, 둘은 같다. 대상을 직접 비교하는 대신, 그들이 받는 (혹은 보내는) 화살표들의 집합을 비교하면 된다는 뜻이죠.

이것이 요네다의 힘입니다. 분배법칙의 두 집합을 직접 들여다보는 대신, 각자가 만들어 내는 \(\operatorname{Hom}\) 집합이 같음을 보이면 끝입니다. 마지막 재료 하나만 더 챙기면 됩니다.


4짝짓기와 커링 — 지수 대상

집합 \(B\)에서 \(C\)로 가는 함수 전체를 \(C^B\)라 씁시다(지수 대상). \(|C^B|=|C|^{|B|}\)이죠. 그러면 두 변수 함수와 "함수를 돌려주는 함수" 사이에 유명한 대응이 있습니다:

커링 (currying) — 곱–지수 수반
$$\operatorname{Hom}(A\times B,\;C)\;\cong\;\operatorname{Hom}(A,\;C^B).$$ "쌍 \((a,b)\)를 받아 값을 주는 함수"는, "\(a\)를 받아 함수 \(b\mapsto\,\)값을 돌려주는 함수"와 같다.

입력 \((a,b)\)를 한꺼번에 받느냐(짝짓기·pairing), 하나씩 받느냐(커링·currying)의 차이일 뿐, 담긴 정보는 똑같습니다. 수로 검산해도 맞아떨어지죠 — 양변의 크기가 \(|C|^{ab}=(|C|^{b})^{a}\)로 같습니다.

이 대응의 진짜 이름은 "수반(adjunction)"
커링은 함자 \(-\times B\)가 함자 \((-)^B\)를 오른쪽 수반으로 가진다는 말과 같습니다(\(-\times B\dashv(-)^B\)). 같은 이유로 \(A\times-\) 역시 왼쪽 수반이죠. 그리고 범주론의 황금률 한 줄 — "왼쪽 수반은 쌍대극한(특히 쌍대곱)을 보존한다." 이 한마디가 사실상 분배법칙입니다. 곧 손으로도 확인해 보죠.


5증명 — 조각을 맞추다

이제 \(A\times(B+C)\cong(A\times B)+(A\times C)\)를 증명합니다. 요네다에 따라, 모든 \(X\)에 대해 양쪽으로 나가는 화살표 집합이 (자연스럽게) 같음만 보이면 됩니다.

왼쪽 변을 변형해 봅시다:

$$\begin{aligned} \operatorname{Hom}\big(A\times(B+C),\,X\big) &\cong \operatorname{Hom}\big(B+C,\;X^{A}\big) &&\text{(커링)}\\ &\cong \operatorname{Hom}(B,\,X^{A})\times\operatorname{Hom}(C,\,X^{A}) &&\text{(쌍대곱)}\\ &\cong \operatorname{Hom}(A\times B,\,X)\times\operatorname{Hom}(A\times C,\,X) &&\text{(커링 역방향)}. \end{aligned}$$

오른쪽 변은 쌍대곱의 보편 성질 한 번이면 됩니다:

$$\operatorname{Hom}\big((A\times B)+(A\times C),\,X\big)\;\cong\;\operatorname{Hom}(A\times B,\,X)\times\operatorname{Hom}(A\times C,\,X).$$

두 변 모두 \(\operatorname{Hom}(A\times B,X)\times\operatorname{Hom}(A\times C,X)\)로 떨어졌고, 이 동형은 \(X\)에 대해 자연스럽습니다. 요네다 보조정리에 의해 두 대상은 동형 —

$$ \boxed{\,A\times(B+C)\;\cong\;(A\times B)+(A\times C)\,} $$

유한집합에서 원소 개수를 세면 곧 \(a(b+c)=ab+ac\). 곱셈·덧셈의 정의(곱·쌍대곱)와 커링, 요네다 — 세 도구만으로 끝났습니다. 그리고 보다시피, 이 증명 어디에도 "자연수"라는 말이 등장하지 않습니다.


6한 번 증명하면, 어디서나

증명이 쓴 것은 오직 — (1) "합"이 쌍대곱이라는 것, (2) "곱"이 왼쪽 수반이라는 것(커링 가능), 그리고 (3) 요네다뿐입니다. 그러니 이 세 조건을 갖춘 어떤 범주에서도 똑같은 분배법칙이 공짜로 따라옵니다.

이것이 범주론을 쓰는 이유
네 번 증명할 일을 한 번 증명했습니다. 대상을 "관계(화살표)"로만 규정했더니, 집합이든 벡터공간이든 위상공간이든 — 같은 보편 성질을 만족하는 순간 같은 정리를 공유합니다. 추상화는 게으름이 아니라, 한 번의 통찰을 모든 곳으로 운반하는 기술입니다.

7프로그래밍 언어에서 — 타입의 대수

놀랍게도 이 모든 것이 코드에 살아 있습니다. 타입(type)을 대상으로, 함수를 화살표로 보면, 타입의 세계가 바로 하나의 범주입니다(스칼라·하스켈 같은 언어에서 특히 또렷하죠).

그러면 우리의 분배법칙이 그대로 타입의 동형이 됩니다 — 실무에서 쓰는 리팩터링이죠:

// (A, Either[B, C])  ≅  Either[(A, B), (A, C)]
def dist[A,B,C](p: (A, Either[B,C])): Either[(A,B),(A,C)] =
  p._2 match {
    case Left(b)  => Left((p._1, b))
    case Right(c) => Right((p._1, c))
  }

이것이 "대수적 데이터 타입(ADT)"의 대수입니다 — 합과 곱이 타입 위에서 반환(semiring)을 이루고, \(1\)은 유닛 타입, \(0\)은 빈 타입(Nothing)이죠. 그리고 커링은 모든 함수형 언어의 기본기:

// (A, B) => C   ≅   A => (B => C)
val f: ((A, B)) => C = ...
val g: A => B => C  = a => b => f((a, b))   // curry

여기서 더 들어가면 범주론 용어가 코드의 일상어가 됩니다. 함자(Functor)map을 가진 타입 생성자(List, Option), 모나드(Monad)flatMap(bind)과 pure를 가진 것(Option, Future, IO) — "클라이슬리(Kleisli) 범주의 합성"이자 "자기함자 범주에서의 모노이드"입니다(이 한마디를 풀어낸 게 모나드는 그냥 모노이드라니까 편이에요). Scala의 cats, 하스켈의 표준 라이브러리가 이 어휘로 짜여 있죠.

한 바퀴 돌아온 자리
초등 분배법칙을 증명하려고 꺼낸 "보편 성질·요네다·수반"이, 그대로 타입을 설계하고 프로그램을 합성하는 문법이 되어 있습니다. 추상적 헛소리(abstract nonsense)라 놀림받던 범주론이, 실은 가장 실용적인 공통어였던 셈이죠.


8맺으며

\(a(b+c)=ab+ac\)는 작습니다. 하지만 그것을 "관계로만" 다시 증명하는 순간, 우리는 자연수라는 비좁은 방을 나와 집합·기수·벡터공간·위상공간·아벨군, 그리고 코드의 타입까지 한 번에 내다보는 창을 얻습니다. 범주론이 약속하는 건 늘 같습니다 — 대상이 무엇인지 잊고 어떻게 이어지는지를 보라. 그러면 한 번의 증명이 모든 세계의 증명이 된다.


참고 자료