https://soooprmx.com/archives/6025

배열은 Swift에서 동일 타입의 원소를 하나 이상 가질 수 있는 순서있는 집합을 나타내는 자료 구조이다. 배열은 Swift 뿐만 아니라 거의 모든 프로그래밍 언어에서 가장 기본적이며 중요한 자료 구조 중 하나인데, 특히 함수형 프로그래밍 언어에서는 배열을 리스트라고 하는 재귀적인 데이터 구조를 사용하여 일련의 연속적인 값들을 집합으로 다루게 된다. 배열 혹은 리스트에 대한 연산 중에서 가장 대표적인 것은 맵핑 연산이다. 하스켈의 fmap 은 임의의 타입의 원소를 갖는 리스트의 각 원소에 대해 주어진 함수를 적용하여 새로운 리스트를 생성한다. 그리고 대부분의 배열을 지원하는 현대 언어들도 비슷한 기능을 가지고 있다. 자바 스크립트의 Array와 Swift의 Array도 공통적으로 map이라는 메소드를 통해서 변형(transform)함수를 받아 자신의 원소들에게 적용하여 유사한 동작을 수행한다.

보통은 이러한 map 동작을 배열 타입의 메소드로 이해하고 (그것이 틀린 말은 아니지만) 사용한다. 여기서 배열은 일종의 자료 구조로서의 기능의 구현으로 보는 시각이다. 그런데 조금 다른 시각으로 이를 바라 볼 수 있지 않을까.

배열은 하나 이상의 값을 갖고 있는 컨테이너라고 보는 것이다. 이 역시 너무나 당연한 사실이기 때문에 받아들이는데 별 어려움이 없을 것이다. let array = [1, 2, 3, 4]라는 표현은 arr이 정수 4개가 들어있는 배열이며, 배열 전체를 봤을 때는 내부에 정수 값 4개를 감싸고 있는 컨테이너인 셈이다.

이 때 map 동작은 컨테이너 내부에 들어있는 각각의 정수값에 어떠한 변형을 가한다. 이 지점에서도 관점을 컨테이너 중심으로 옮기게 되면 maptransform 이라는 어떤 A 타입의 값을 B타입의 값으로 바꿀 수 있는 함수를 컨테이너의 “내부”에 적용하는 연산이라 볼 수 있다.

[1,2,3,4]라는 리스트에 y = x * x 라는 변형을 적용했다.

https://s3-us-west-2.amazonaws.com/secure.notion-static.com/d76803a6-7c2c-4844-9429-4448dab07f45/functor-500x143.png

이 때 맵핑 자체를 배열에 대한 연산으로 보기 위해서는 .map 이라는 메소드 형태보다는 fmap 이라는 자유 함수 형태로 보는 것이 좀 더 편할 것이다. 따라서 다음과 같은 함수를 하나 선언한다.

func fmap<A, B>(_ anArray: [A], _ tranform: (A) -> B) -> [B] {
  return anArray.map(transform)
}

이제 위에 있는 그림은 fmap([1, 2, 3, 4], { $0 * $0 })이라고 쓰여질 수 있다. 이는 배열 [1, 2, 3, 4]에 대해서 입력값을 제곱하는 “변형”을 각 원소에 적용한다는 것이다. 내부 원소에 특정한 변형을 적용하는 것을 “변형을 사상한다(mapping over an transform)”라고 표현할 수 있다.

이 과정에서 타입과 관련해서 원소의 타입은 변형함수에 의해서 변경될 수 있지만 ((A) -> B ) 배열(혹은 리스트)라는 컨테이너의 구성 자체가 바뀌지는 않는다는 것이다.

Functor

Functor(함자)는 어떤 변형(함수나 연산)을 사상할 수 있는 것을 말한다. 즉 어떤 것이든 변형을 적용할 수 있다면 그것은 functor가 된다고 할 수 있다. 앞에서 살펴본 예에서 배열(리스트)은 이미 functor라는 것을 확인했다. 수학에서 functor는 몇 가지 규칙을 따르는데, 이는 다음과 같다.

  1. 항등 사상의 보존 : 항등함수 id에 대해서 X에 대한 사상 F는 를 만족한다.
  2. 사상 합성의 보존 : 와 에 대해서 를 만족한다.

이 규칙에 대해서 크게 신경 쓸 필요는 없다. 찬찬히 읽어보면 그냥 당연한 소리를 써놨을 뿐인 셈이다. Functor는 결국 변형 연산을 적용할 수 있는 그 어떤 것이든 될 수 있고, 어떤 타입이 functor라면 그 타입을 받는 fmap 함수를 작성할 수 있다는 말이 된다.

다른 functor 들

그렇다면 배열만이 가능한 functor인가? 그렇지는 않다. 집합(Set)타입에 대해서 생각해보자. Swift의 Array와 Set은 중복을 허용하고, 순서가 있는지에 대한 차이가 있을 뿐, “여러 개의 값을 내재하는 컨테이너”라는 맥락은 동일하다. 그리고 위에서 본 다이어그램과 마찬가지로 임의의 변형 f를 사상하는 동작 자체가 Set이라고 불가능하지는 않다.

실제로 Xcode 9.0부터 지원하는 Swift 4에서는 이 Set 타입에 대한 map 동작이 기본적으로 라이브러리에 포함되어 있다.