Skip to content

Latest commit

 

History

History
182 lines (114 loc) · 15.4 KB

category-the-essence-of-composition.md

File metadata and controls

182 lines (114 loc) · 15.4 KB

합성(Composition)의 정수, 카테고리

원본 사이트: Category Theory for Programmers


지난 글에서 보여주신 긍정적인 반응은 엄청났습니다. 동시에 이는 저에게 많은 사람이 기대하고 있다는 사실을 깨닫게 해줘서 부담도 됐습니다. 제가 작성할 글에 독자분들이 실망할까 봐 걱정도 됩니다. 어떤 독자분들은 책이 더 실용적이라고 할 수도 있고 또 다른 독자분들은 더 추상적이라고 할 수도 있습니다. 어떤 독자분들은 C++을 싫어하고 하스켈로 만든 예제를 더 좋아할 수도 있습니다. 또 어떤 독자분들은 하스켈도 싫어해서 자바로 만든 예제를 원하실 수도 있습니다. 그러니 이 책은 모두를 만족시킬 수는 없을 것입니다. 어느 정도 타협 선을 지키며 제가 지금까지 느꼈던 아하! 들을 공유할 수 있는 자리가 되었으면 좋겠습니다. 기본부터 시작해볼까요?

카테고리는 당황스러울 정도로 간단한 개념입니다. 카테고리는 객체와 그 객체 사이의 화살표들로 이루어져 있습니다. 그래서 카테고리는 그림으로 표현하기가 쉽습니다. 객체는 원이나 점으로 표현할 수 있고, 화살표는 화살표로 표현하면 됩니다. (다양성을 위해 저는 객체를 돼지들로, 화살표는 폭죽으로 표현하려고 합니다) 하지만 카테고리의 정수는 합성입니다. 원하신다면 합성의 정수가 카테고리라고도 할 수 있습니다. 객체 A에서 객체 B로 가는 화살표와 객체 B에서 객체 C로 가는 화살표를 합성하면 객체 A에서 객체 C로 가는 화살표를 만들 수 있습니다.

카테고리에서는 A에서 B로 가는 화살표와 B에서 C로 가는 화살표가 있다면 그것들의 합성을 통해 반드시 A에서 C로 직접 가는 화살표를 만들 수 있습니다. 하지만 사상(morphism)이 없다는 점에서 이 다이어그램은 완전한 카테고리라고 할 수는 없습니다.

화살표를 함수로 보기

이미 말이 안 될 정도로 추상적인가요? 절망하지 마세요. 구체적으로 말해보겠습니다. 사상(morphism)이라고도 불리는 화살표를 함수라고 생각해보겠습니다. 여기 A 타입을 받아서 B를 반환하는 f라는 함수가 있습니다. 그리고 B를 받아서 C를 반환하는 함수 g가 있습니다. 우리는 f에서 g로 결과를 넘김으로써 합성할 수 있습니다. 이제 A를 받아서 C를 반환하는 함수를 만드셨습니다.

수학에서의 합성은 g∘f와 같이 작은 원으로 표시합니다. 합성의 순서는 오른쪽에서 왼쪽입니다. 혼란스러울 수 있습니다. 이와 비슷한 문법은 Unix에서도 찾아볼 수 있습니다.

lsof | grep Chrome

또는 F#의 >>처럼 왼쪽에서 오른쪽으로 가는 문법을 사용하셔도 됩니다. 하지만 수학과 하스켈에서는 오른쪽에서 왼쪽으로 합성을 합니다. 그리고 g∘f는 "g after f"라고 읽으시면 됩니다.

C 코드를 통해 좀 더 분명하게 작성해보겠습니다. A 타입을 받아서 B 타입을 반환하는 함수 f 를 작성해보겠습니다.

B f(A a);

다른 함수도 작성해보겠습니다.

C g(B b);

그 합성 결과는 다음과 같습니다.

C g_after_f(A a)
{
    return g(f(a));
}

C로 표시한 것도 잘 보시면 오른쪽에서 왼쪽으로 합성(g(f(a)))하는 것을 보실 수 있습니다.

할 수만 있다면 C++ 표준 라이브러리에 두 함수를 받아서 그들의 합성을 반환하는 템플릿이 있다고 말씀드리고 싶지만 아쉽게도 없네요. 언어를 바꿔볼까요? 하스켈로 같은 내용을 작성해보겠습니다.

f :: A -> B

비슷하게 g도 작성한다면 다음과 같습니다.

g :: B -> C

위 둘을 합성해보면 아래와 같이 될 것입니다.

g . f

하스켈이 이렇게 쉽게 표현하는 것을 보고 나면 간단한 함수형 개념도 표현하지 못하는 C++은 조금 부끄러울 수도 있습니다. 하스켈에선 유니코드 문자를 사용할 수 있어서 합성을 다음과 같이 표현할 수 있습니다.

g ∘ f

심지어 유니코드 문자인 더블 콜론과 화살표도 사용할 수 있습니다.

f ∷ A → B

첫 번째 하스켈 수업입니다. 더블 콜론은 "이러한 타입을 가지고 있다."를 의미합니다. 두 타입 사이에 화살표를 넣으면 함수 타입을 만들 수 있습니다. 그리고 두 함수 사이에 마침표(또는 유니코드 문자 원)를 넣으면 합성을 표현할 수 있습니다.

합성의 성질

어떤 카테고리의 합성이라도 반드시 만족하는 아주 중요한 두 가지 성질이 있습니다.

  1. 합성은 결합성을 가집니다. 합성이 가능한 세 가지 사상(morphism)인 f,g,h가 있다고 해보겠습니다. (합성할 수 있으니 각자 끝에서 끝이 이어지겠죠) 이들을 합성할 땐 괄호도 없앨 수 있습니다. 수학적으로 표현한다면 다음과 같이 표현할 수 있습니다.
h∘(g∘f) = (h∘g)∘f = h∘g∘f

하스켈의 수도 코드입니다.

f :: A -> B
g :: B -> C
h :: C -> D
h . (g . f) == (h . g) . f == h . g . f

("수도"라고 말한 이유는 각 함수에 대해 동등함이 선언되지 않았기 때문입니다)

결합성은 함수를 다룰 때 꽤 명확하게 나오지만 다른 카테고리에서는 그만큼 명확하진 않습니다.

  1. 모든 A 객체에 대해서 합성의 유닛이 되는 화살표가 존재합니다. 이 화살표는 객체 자기 자신을 가리키는 루프입니다. 합성의 유닛이 된다는 것은 A에서 시작하거나 A로 끝나는 화살표와 합성하면 똑같은 화살표를 돌려받는다는 의미입니다. A 객체에 대한 유닛 화살표는 idA(identity on A, A에 항등)라고 부릅니다. 수학적으로 표현하면 A를 받아서 B를 반환하는 f에 대해서 다음과 같이 표현할 수 있습니다.
f∘idA = f

그리고

idB∘f = f

함수를 다룰 때 항등함수로 구현된 항등 화살표는 인자로 온 값을 다시 반환합니다. 구현은 모든 타입에 대해 같으며 이는 이 함수가 보편적으로 다형성이라는 것을 의미합니다. C++에선 다음과 같은 템플릿으로 표현할 수 있습니다.

template<class T> T id(T x) { return x; }

물론 C++에선 그렇게 쉽지 않은데 왜냐하면 넘기는 값이 무엇인지뿐만 아니라 어떻게 넘기는지도 알아야하기 때문입니다.

하스켈에선 항등함수가 표준 라이브러리(Prelude라고 불립니다)의 일부입니다. 다음은 그것의 선언과 정의입니다.

id :: a -> a
id x = x

보시다시피 하스켈에서 다형성 함수는 식은 죽 먹기입니다. 선언을 보면 타입을 타입 변수로 변환하기만 하면 됩니다. 약간의 요령을 알려드리자면 구체적인 타입의 이름은 언제나 대문자로 시작하고 타입 함수의 이름은 언제나 소문자 a 로 시작합니다. 여기서 a는 모든 타입을 나타냅니다.

하스켈 함수 정의는 정식 파라미터와 함께 함수의 이름으로 이루어져 있습니다. 여기선 x 하나네요. 함수의 바디 부분은 등호를 따릅니다. 이 간결함은 처음 배우는 분들에겐 놀라울 수도 있지만 완벽하게 맞아떨어진다는 것을 바로 이해하실 수 있을 것입니다. 함수의 정의와 함수 호출은 함수형 프로그래밍의 빵과 버터급으로 필수적이어서 그들의 문법은 최대한 간결해졌습니다. 인자 목록에 괄호가 없을 뿐만 아니라 인자 사이의 콤마조차도 없습니다. (이는 나중에 다중 인자를 가진 함수를 정의할 때 보실 수 있을 것입니다)

함수의 바디 부분은 언제나 표현식이 차지합니다. 함수엔 명령문은 존재하지 않습니다. 함수의 결과는 표현식입니다.

하스켈의 두 번째 수업이 끝나버렸네요.

항등 조건은 다음과 같이 작성될 수 있습니다. (여기도 수도 하스켈입니다)

f . id == f
id . f == f

이런 질문이 생길 수도 있습니다. "왜 아무것도 하지 않는 항등함수에 대해 아무도 뭐라고 하지 않는거지? 그렇다 치더라도 0은 왜 뭐라고 하는거지? 0은 아무것도 없다는 상징인데." 고대 로마인들은 0이 없는 숫자 체계를 가지고 있었고 그들은 0 없이도 오늘날까지 무너지지 않은 훌륭한 도로와 송수로를 만들 수 있었습니다.

0이나 id 같은 중립적인 값은 상징적인 값을 다룰 때 엄청나게 도움이 됩니다. 그게 바로 0의 개념과 친근하던 아랍인과 페르시아인들에 비해 로마인들이 대수학(algebra)을 잘하지 못했던 이유입니다. 항등함수는 고계 함수(higher order function)에 인자로 넣거나 반환 값으로 받을 때 아주 편리합니다. 고계 함수는 함수를 상징적으로 조작하는 것을 가능하게 해주는 개념입니다. 함수의 대수학이라고 할 수 있습니다.

요약하겠습니다. 카테고리는 객체와 화살표(사상, morphism)로 이루어져 있습니다. 화살표는 합성될 수 있으며 합성은 결합성을 가집니다. 모든 객체는 합성에서 쓰일 수 있는 유닛을 만드는 항등 화살표를 가집니다.

합성은 프로그래밍의 정수입니다

함수형 프로그래머들은 문제에 접근하는 특유의 방식이 있습니다. 그들은 철학적 질문부터 합니다. 예를 들면 인터랙티브 프로그램을 디자인한다고 했을 때 그들은 인터랙션이란 무엇인가? 와 같은 질문을 합니다. Conway의 라이프 게임을 구현할 때도 그들은 아마 라이프의 의미에 대해 곰곰이 생각할 것입니다. 이런 맥락에서 저는 묻고 싶습니다. 프로그래밍이란 무엇일까요? 가장 기본적인 단계로 설명하자면 프로그래밍은 "메모리 주소 x의 컨텐츠를 가지고 EAX 레지스터의 컨텐츠에 추가해라" 같이 컴퓨터가 무엇을 할지 말하는 것입니다. 하지만 어셈블리를 프로그래밍할 때 우리가 컴퓨터에 전달하는 것은 좀 더 의미적인 표현입니다. 우리는 사소한 문제를 풀고 있는 것입니다. (그게 사소하지 않았다면 컴퓨터가 아니라 다른 것의 도움을 받았겠죠) 우리는 어떻게 문제를 해결하나요? 우리는 큰 문제를 더 작은 문제로 분해합니다. 작은 문제가 여전히 크다면 또 분해하고 이를 반복합니다. 마지막엔 각각의 작은 문제들을 해결할 수 있는 코드를 작성합니다. 그 후엔 프로그래밍의 정수라 할 수 있는 과정을 거칩니다. 우리는 각각의 문제를 해결할 수 있는 코드를 이어붙여서 더 큰 문제를 해결합니다. 여기서 조각을 되돌려 놓을 수 없는데 분해한다는 것은 말이 되지 않습니다.

계층적인 분해와 재합성의 과정은 컴퓨터가 우리에게 강요하는 것은 아닙니다. 이는 인간이 할 수 있는 생각의 한계를 반영하는 것입니다. 우리의 뇌는 한 번에 적은 수의 개념만 받아들일 수 있습니다. 이와 관련되어 가장 많이 언급되는 심리학 논문은 우리가 생각할 수 있는 정보 청크의 수는 7 ± 2 라는 내용의 마법의 수 칠, 더하거나 빼기 이(The Magical Number Seven, Plus or Minus Two)입니다. 인간의 단기 기억에 대한 세부 사항은 다를 수 있지만, 우리 모두 그게 한정돼있다는 것은 알고 있습니다. 핵심은 우리가 잡탕처럼 섞인 객체 혹은 스파게티처럼 얽힌 코드는 이해하기 어렵다는 것입니다. 한 마디로 프로그램이 보기 좋으라고 구조를 잘 짜는 것이 아닌 우리의 뇌의 효율적인 이해를 위해 구조를 잘 짜야한다는 것입니다. 우리는 종종 어떤 코드를 보고 우아하거나 아름답다고 말하지만, 이는 쉽게 말하자면 그냥 한정된 인간의 생각이 잘 처리할 수 있다는 것을 의미합니다. 우아한 코드는 우리의 정신적인 소화 체계가 잘 이해할 수 있도록 적합한 사이즈와 적절한 숫자의 청크를 만듭니다.

그래서 프로그램 합성의 적절한 청크는 뭘까요? 표면적은 부피가 늘어나는 속도보다 느립니다. (저는 이 비유를 좋아하는데 왜냐하면 기하학적으로 표면은 사이즈의 제곱의 값과 같이 늘어나고, 사이즈의 세제곱 값을 가지는 부피보다는 느리게 늘어나기 때문입니다) 면적은 청크를 합성하기 위해 알아야 할 정보라고 볼 수 있습니다. 그리고 부피는 그것을 구현하기 위해 알아야 할 정보라고 볼 수 있습니다. 아이디어는 이렇습니다. 청크가 구현되면 우리는 이것의 구현을 잊고 다른 청크와 어떻게 작동하는지에만 집중할 수 있습니다. 객체 지향 프로그래밍에서는 이 표면이 객체의 클래스 정의 또는 추상 인터페이스입니다. 함수형 프로그래밍에서는 함수의 선언이 표면이 되겠습니다. (많이 생략했지만, 요지는 변함없습니다.)

카테고리 이론은 객체의 내부를 살펴보지 않도록 열심히 막는다는 의미에서 극단적입니다. 카테고리 이론의 객체는 추상적이고 모호한 독립체입니다. 우리가 알 수 있는 것은 오직 화살표를 사용해서 연결되는 다른 객체와의 관계입니다. 이것은 인터넷 검색 엔진이 웹사이트를 들어감과 나감을 통해 랭킹을 매기는 방법과 유사합니다. 객체 지향 프로그래밍에서는 이상화된 객체는 오직 그것의 추상 인터페이스에서만 화살표 역할을 하는 메소드와 함께 볼 수 있습니다. 다른 객체와 합성하는 방식을 이해하기 위해 객체의 구현 방식을 파헤쳐야 하는 순간, 여러분은 여러분의 프로그래밍 패러다임의 이점을 잃게 될 것입니다.

도전 과제

  1. 가장 좋아하는 언어로 최고의 방법을 사용해서 항등함수를 작성해보세요. (여러분이 가장 좋아하는 언어가 하스켈이라면 두 번째로 좋아하는 언어로 해보세요.)

  2. 가장 좋아하는 언어로 합성 함수를 구현해보세요. 두 함수를 인자로 받아서 그들의 합성을 나타내는 함수를 반환하면 됩니다.

  3. 여러분의 합성 함수가 항등을 존중하는지 테스트하는 프로그램을 작성해보세요.

  4. 월드 와이드 웹(www)은 카테고리라고 할 수 있을까요? 그렇다면 링크들은 사상(morphism)일까요?

  5. 사용자를 객체라고 하고 친구 관계를 사상(morphism)이라고 한다면 페이스북은 카테고리일까요?

  6. 방향 그래프가 카테고리가 되는 경우는 언제일까요?

다음글: 타입과 함수


공부 목적으로 번역을 하고 있습니다! 잘못된 점에 대한 이슈나 PR은 언제든지 환영합니다 :)