함수자
프로그래밍에서 에러 핸들링이라는 또 다른 중요한 개념을 살펴본다.
함수자(functor) 라는 새로운 개념을 살펴본다. 이 개념은 순수하게 함수형 방법으로 에러를 다룰 수 있도록 도와준다.
함수자의 아이디어를 이해한 후 두 가지의 실제 함수자(MayBe, Either) 를 구현해본다.
함수자
함수자란 기본적인 객체로, 객체 내의 각 값을 실행할 때 새로운 객체를 실행하는 map 함수를 구현한다.
함수자는 컨테이너다.
간단하게 함수자는 값을 갖고 있는 컨테이너다. 함수자가 기본적인 객체라는 정의에서 이를 살펴봤다. 값을 갖는
간단한 컨테이너를 생성해보며, Container 를 호출해본다.
1 | const Container = function (val) { |
Container는 값을 내부에 저장하기만 한다. 자바스크립트의 모든 데이터형을 전달할 수 있으며, Container 는 이를 저장한다.
Container 프로토타입에서 of 라고 불리는 유용한 메서드를 생성할 수 있는데, new 키워드를 사용하지 않아도
새로운 Container 를 생성할 수 있게 해준다.
1 | Container.of = function(value) { |
map 구현
map 함수가 필요한 이유는 현재 Container에 저장된 값에 대한 함수를 호출할 수 있다.
map 함수는 Container 의 값을 받고 해당 값에 전달된 함수를 적용한 후 결과를 다시 Container 에 넣는다.
1 | Container.prototype.map = function(fn) { |
map 함수는 Container 에 전달된 함수의 결과를 다시 반환하며, 이는 결합 연산을 가능케한다.
1 | Container.of(3).map(double) |
MayBe 함수자
MayBe 함수자는 좀 더 함수적인 방법으로 코드의 에러를 핸들링할 수 있다.
MayBe 구현
MayBe는 함수자의 한 형태로, map 함수를 다른 방식으로 구현한다.
1 | const MayBe = function(val) { |
Container 구현과 유사하다. map 을 구현해본다.
1 | MayBe.prototype.isNothing = function() { |
map 함수는 Container 의 map 함수와 유사하다. MayBe 의 map 은 전달된 함수에 isNothing 함수를
적용해 컨테이너 값이 null 인지 undefined 인지 먼저 확인한다.
간단한 사용자 케이스
MayBe 는 map 에 전달된 함수를 적용하기 전에 null 과 undefined 를 확인한다.
이는 에러 핸들링을 다루는 가장 강력한 추상화다.
1 | MayBe.of("string").map(x => x.toUpperCase()); |
위 코드에서 x 가 null 또는 undefined 일때 에러가 발생한다. 하지만 MayBe 에서 래핑하므로, 에러가 발생하지 않는다.
1 | MayBe.of("George") |
모든 map 함수가 null/undefined 를 받는 것과 상관없이 호출된다. 이 처럼 MayBe 는 모든 undefined, null 에러를 쉽게 다룰 수 있게 도와준다.
Either 함수자
Either 함수자를 만들어 분기 문제를 해결한다.
1 | MayBe.of("George") |
원하는 결과가 출력됐다. 하지만 어떤 분기 (map 호출 두개) 가 undefined 및 null 값에 부합하지 않는지 알 수 없다.
어떤 분기에서 발생된 문제 인지 찾으려면 MayBe 의 분기를 일일이 파고 들어야 한다. 이것이 Either 가 필요한 이유다.
Either 구현
1 | const Nothing = function(val) { |
위 구현에는 Some 과 Nothing 이라는 두 함수가 있다. Some 함수는 Container 를 이름만 바꿔 복사한 것이다.
Nothing 의 map 은 주어진 함수를 실행하지 않고 오히려 반환한다. 다시 말해 Some 에서는 함수를 실행하는데,
Nothing 에서는 실행하지 않는다.
1 | Some.of("test").map(x => x.toUpperCase()); |
Some 과 Nothing 두 객체를 Either 객체로 감싼다.
1 | const Either = { |
Either 는 MayBe 와 다르게 예외 상황이 발생한 경우 null 값이 반환되지 않는다.
1 | const books = [ |
위와 같이 데이터가 있다고 할때, author 가 Axel 을 포함하고 있을 경우 console.log, 포함하고 있지 않을 경우 console.error 를 실행하고자 한다.
console.error 를 실행할때 MayBe 같은 경우 null 값이 반환되기 때문에 어떤 값에서 에러가 발생하는지 알 수 없다.
이 문제를 Either 를 사용하여 해결하고자 한다.
우선 Some 과 Nothing 에 다음과 같은 프로토타입을 추가해준다.
1 | Some.prototype.isNothing = function() { |
1 | const findBookById = curry((id, books) => { |
validateBookAuthor 함수 부분에서 Axel 이 포함되어 있지 않은 경우 Nothing.of 를 실행하여 분기 처리를 해준다.
모나드
모나드(Monad)
- chain 메소드를 구현한 객체다.
- chain 메소드는 모나드가 가진 값에 함수를 적용해서 새로운 모나드(chain 을 갖고 있는)를 반환해야 한다.
모나드는 chain 메서드를 갖는 함수자다. 즉 이것이 모나드다.
1 | Maybe.prototype.join = function() { |
MayBe 함수자를 chain 을 추가해 모나드로 만들었다. 반복적인 map 이 중첩된 값에 매우 효율적이다.
참조: 함수형 자바스크립트 입문 2/e
참조: nakta.log