프로필 사진
Sojin Park
Frontend Dev

함자
List와 Promise를 묶는 개념인 함자란 무엇인가
2019. 2. 2.

함자Functor는 함수형 프로그래밍에서 사용하는 강력한 개념 중 하나이다. 함자는 범주론에서 두 범주를 연결하는 역할을 수행한다.

범주Category에는 대상Object과 사상Morphism이 있으므로, 두 범주 C\textbf{\text{C}}D\textbf{\text{D}}를 연결하려면 각 범주의 대상과 사상을 연결하는 것이 필요하다.

첫 번째로 각 범주의 대상을 살펴보자. 함자는 두 범주를 연결해야 하므로, FF가 범주 C\textbf{\text{C}}D\textbf{\text{D}}를 연결하는 함자라면, FFC\textbf{\text{C}}의 대상 모두를 범주 D\textbf{\text{D}}로 옮겨야 한다. 즉, aa가 범주 C\textbf{\text{C}}의 대상이라면, 함자 FFaa를 범주 D\textbf{\text{D}}에 있는 대상 FaF,a로 옮긴다. (이때 FaF,aaaFF에 의한 상Image이라고 한다.)

여기에서 두 집합 사이의 관계를 정의하는 함수와 닮은 점을 발견할 수 있는데, 함자 FF는 범주 C\textbf{\text{C}}의 대상을 정의역으로 하고 범주 D\textbf{\text{D}}의 대상을 공역으로 하는 함수Function로 생각할 수 있다.

두 번째로 범주를 구성하는 두 가지 요소 중 다른 하나인 사상에 대해 생각해 보자. 대상과 마찬가지로 함자 FF는 범주 C\textbf{\text{C}}에 있는 사상 ff를 범주 D\textbf{\text{D}}에 있는 사상 FfF,f로 옮겨야 한다. 단, 이때 함자가 가지고 있는 중요한 조건 중 하나가 있다. 함자는 사상을 아무 규칙 없이 옮겨서는 안 되고, 원래 사상이 가지고 있는 연결성을 유지하여야 한다. 예를 들어 범주 C\textbf{\text{C}} 안에 사상 ff가 있어서 다음과 같이 대상 aabb를 연결한다면,

f::ab f :: a \rightarrow b

함자 FF에 의한 ff의 상 FfF,fD\textbf{\text{D}} 안의 FaF,aFbF,b를 다음처럼 연결하여야 한다.

Ff::FaFb F,f : :: F,a \rightarrow F,b

위에서 보듯 함자는 하나의 범주를 다른 범주로 옮기지만 그 구조를 보존하는 특성이 있다. 즉, 한 범주 C\textbf{\text{C}}에서 연결되어 있는 대상들이라면 다른 범주 D\textbf{\text{D}}에도 연결된다.

특히 범주 C\textbf{\text{C}}의 사상 hh가 사상 ffgg의 합성으로 구성된다면

h::g.f h :: g , . , f

함자 FF에 의한 상 FhF,h도 역시 FfF,fFgF,g의 합성으로 구성되어야 한다.

Fh::Fg.Ff F , h :: F , g :.: F , f

또한 C\textbf{\text{C}}에 있는 모든 항등 사상 id\text{id}D\textbf{\text{D}}에 있는 항등 사상으로 옮겨져야 한다.

Fida=idFa F , \text{id}_{a} = \text{id}_{F , a}

함자가 가지는 중요한 특성이 이곳에 있다. 대상의 측면에서 볼 때 함자는 범주 C\textbf{\text{C}}의 대상들을 범주 D\textbf{\text{D}}의 대상으로 아무렇게나 옮겨도 된다. 예를 들어 C\textbf{\text{C}}의 여러 대상들을 D\textbf{\text{D}}에서는 하나의 대상으로 몰아서 합쳐버린다거나 하는 것도 문제가 되지 않는다. 그러나 사상의 측면에서 볼 때 C\textbf{\text{C}}가 가지는 연결성은 유지하여야 해서, C\textbf{\text{C}}에서 연결되어 있던 대상들은 D\textbf{\text{D}}에서도 연결되어야 한다. 어떤 의미에서는 미적분학Calculus에서 연속함수끼리의 합성은 반드시 연속함수인 것과 유사하다.

자기함자Endofunctor

함자 중에서 특별히 출발 범주와 도착 범주가 같은 함자, 즉 범주 C\textbf{\text{C}}를 같은 범주 C\textbf{\text{C}}로 옮기는 함자를 자기함자Endofunctor라고 한다.

프로그래밍에서는 특별한 이야기가 없으면 대체로 함자는 자기함자를 의미한다. 예를 들어 List (또는 JavaScript에서는 Array)는 (자기)함자라고 볼 수 있는데, map 함수를 통해 List 범주를 자기 자신 List 범주로 옮기기 때문이다. map에 제공된 함수가 순수 함수Pure function라면 연결성이 어떻게 보존되는지 실험해 보는 것도 무척 재미있다. 예를 들어

const names: Array<string> = [
    'Jin',
    'Mason',
    'Incleaf',
    'Makesource'
];

const nameLengths = Array<number> = names.map(x => x.length);
// —> [3, 5, 7, 10]

와 같은 코드는 ArrayArray로 옮기면서, 대상 stringnumber 사이의 관계인 length 함수가 가지는 연결성을 보존한다. 구체적인 코드로 예시를 들어보자면,

function getLength(x: string) {
  return x.length;
}

const name = 'Sojin';

// 이름의 길이를 구하고, 배열로 만듦
[getLength(name)]; // —> [5]

// 이름을 배열로 만들고, 원소의 길이를 구함
[name].map(getLength); // —> [5]

처럼 먼저 배열로 감싼 뒤 length 함수로 map을 걸거나, length를 구한 뒤 배열로 감싸거나 해도 결과는 같다. 또한

function square(n: number) {
  return n * n;
}

function getLength(x: string) {
  return x.length;
}

const name = 'Sojin';

[square(getLength(name))]; // [25]

[name].map(getLength).map(square); // [25]

처럼 사상이 합성되는 성질도 잘 보존한다.