📖 구조 노트 목차 Chapter 16 읽는 중

16. 다른 길로 옮겨도 어긋나지 않는다

구조 노트 Chapter 16 2026. 07. 16.

배송 관리 화면에 주문 목록이 쭉 나열되어 있다. 첫 번째 주문을 고른 뒤 그 주문을 넣은 고객의 이메일을 찾을 수도 있다. 반대로 주문 목록 전체에서 고객 이메일만 먼저 뽑아 새로운 목록을 만든 뒤, 그 목록의 첫 번째 이메일을 고를 수도 있다. 순서는 다르지만 결과는 같다. 어느 길을 택해도 결국 첫 번째 주문을 넣은 고객의 이메일을 얻게 된다.

이 장면을 코드로 옮기기 위해 15장의 Optional과 목록 사이에 다리 하나를 놓아 보자. 목록에서 첫 번째 원소를 꺼내는 함수다. head(orders)는 주문이 하나라도 있으면 첫 번째 주문을 담은 Optional을 돌려주고, 목록이 비어 있으면 빈 Optional을 돌려준다. 목록이라는 상자와 Optional이라는 상자는 서로 다른 세계에 있지만, head는 그 둘을 이어 주는 통로가 된다.

이제 앞의 두 경로를 코드로 적어 보면 head(orders.map(getEmail))head(orders).map(getEmail)이 된다. 하나는 목록 전체에 이메일을 뽑는 함수를 먼저 적용한 뒤 첫 번째 원소를 꺼내고, 다른 하나는 첫 번째 주문을 먼저 꺼낸 뒤 이메일을 뽑는다. 계산 순서는 다르지만 결과는 항상 같다. 화면에서 확인했던 사실이 코드 위에서도 그대로 성립하는 셈이다.

중요한 것은 이것이 getEmail이라는 특정 함수 하나에서만 우연히 성립하는 일이 아니라는 점이다. 주문에서 배송지를 꺼내는 함수를 넣어도, 결제 금액을 꺼내는 함수를 넣어도 결과는 달라지지 않는다. 어떤 함수를 집어넣더라도 head(orders.map(f))head(orders).map(f)는 언제나 같은 값을 만든다. head라는 다리는 함수가 지나가는 순서를 마음대로 바꿔도 전체 결과가 흔들리지 않는 특별한 성질을 갖고 있는 것이다.

수학에서는 이런 다리를 **자연변환(natural transformation)**이라고 부른다. 자연변환은 두 펑터 사이를 잇는 다리다. headList<Order>에서 Optional<Order>로, List<string>에서 Optional<string>로, 어떤 타입이 오더라도 같은 규칙으로 만들어진 다리들의 모음이다. 그리고 그 다리들은 어떤 함수가 지나가더라도 먼저 건넌 뒤 함수를 적용한 결과와, 함수를 적용한 뒤 건너간 결과가 항상 일치한다.

13장부터 여기까지 시선은 조금씩 높아져 왔다. 처음에는 관계를 살폈고, 다음에는 두 구조가 같은지 비교했다. 이어서 구조를 다른 구조로 옮기는 규칙을 살폈고, 이번에는 그런 옮김과 옮김 사이를 잇는 다리까지 살펴보았다. 단계는 하나씩 올라갔지만 질문은 한 번도 바뀌지 않았다. 무엇이 달라져도 되고, 무엇은 끝까지 보존되어야 하는가.

head가 특별한 이유는 목록마다 다른 규칙을 따로 정했기 때문이 아니다. 주문 목록에도, 이메일 목록에도, 숫자 목록에도 “첫 번째 원소를 꺼낸다”는 같은 규칙 하나만 적용한다. 그런데도 어떤 함수를 사이에 끼워 넣든 순서를 바꿔도 결과는 어긋나지 않는다. 이것은 사례를 하나씩 맞춰 얻은 우연이 아니라, 모든 사례를 한꺼번에 관통하는 하나의 구조다.

지금까지 살펴본 범주론의 개념들은 결국 이런 감각을 가리키고 있었다. 대상이 바뀌고, 관계가 바뀌고, 옮기는 방법이 바뀌어도 그 뒤에 반복해서 나타나는 같은 모양을 발견하는 감각이다. 그렇다면 사람은 이런 모양을 어떻게 배우게 되는 걸까.