• 함수형 프로그래밍을 접하면 자주 듣게되는 키워드에 대해, 최대한 간단하게 제가 이해한 바를 적어보도록 하겠습니다. 이번 포스팅을 위해 링크1, 링크2, 링크3, 링크4를 참고하였습니다.
Functors
  • Functor란, 쉽게 말해 매핑될 수 있는 것들을 말합니다. (OOP 스럽게 얘기하자면 Mappable 의 implementation 입니다.) functor는 map 함수에 의해 매핑 될 수 있는 성질을 가집니다. 간단한 예시를 살펴 봅시다.
console.log([1,2,3].map(x=>x+3)) //[4,5,6]
  • 배열은 Functor 입니다. 위에서 볼 수 있듯 배열의 각 요소가 함수에 의해 1대 1로 맵핑 될 수 있기 때문에 정의를 충족합니다. 사실상 값을 가지는 거의 모든 것들이 Functor로 취급될 수 있습니다. 단순한 변수여도 좋고, 객체, 배열 등등 아주 다양하죠. 아래의 클래스를 한번 봅시다.
class OrdinaryWrapper {
    constructor (value) {
        this.value = value
    }
    map (f) {
        return new OrdinaryWrapper(f(this.value))
    }
}
const addThree = (a) => { return a+3; }

const temp = new Wrapper(10);
console.log(temp); // value: 7

const mappedTemp = something.map(addThree);
console.log(mappedTemp) // value: 10
  • OrdinaryWrapper의 인스턴스는 Functor입니다. 함수를 적용시키고 난 후에도 같은 OrdinaryWrapper 클래스가 나왔기 때문입니다. 매핑 가능함이란 .map 오퍼레이션이 끝난 후에도 본래의 형(type)을 보존하는 성질이라 할 수 있습니다. 수학에서의 ‘~계에 닫혀있다’와 비슷한 맥락같군요.

  • 어떤 추상적 개념이 모호할때는 적어도 무엇이 그것이 아닌지 알아보는 것이 더 도움이 될 때가 있습니다. 그럼 무엇이 Functor가 아닐까요? ‘매핑이 될 수 없음’이 무슨말인지 한번 알아봅시다.

class QuasiFunctor {
  constructor (object) {
    this.object = object
  }
  map (f) {
    const mapped = { }
    for (const key of Object.keys(this.object)) {
      mapped[key] = f(this.object[key])
    }
    return mapped
  }
}
  • 위의 QuasiFunctor는 functor가 아닙니다. 왜냐면 .map(f) 오퍼레이션이 QuasiFunctor를 반환하고 있지 않기 때문입니다. 즉 .map을 하고나면 객체가 본래의 모습을 잃어버립니다. 어떻게 아냐고요? 반환된 값은 .map이 정의 되지 않은 object기 때문에 .map을 실행할 수가 없습니다. functor는 map-specific 함을 알 수 있습니다.
const sample = { name: "ReasonML", popularity: 10 }
const mappedSample = new QuasiFunctor(sample)
mappedSample.map(doSomething).map(doSomething)
// =! mappedSample.map(...).map is not a function!!!

생각보다 읽을 거리가 많아져서, Applicative와 Monads는 내일 다뤄보겠습니다.