함수형 프로그래밍과 JavaScript ES6+ 강의를 듣고 정리한 내용입니다.
✅ Klesli Composition 관점에서의 Promise
Promise는 Klesli Composition을 지원하는 도구이다.
Klesli Composition은 오류가 있을 수 있는 상황에서 함수 합성을 안전하게 하기 위한 하나의 규칙이다.
오류가 있을 수 있는 상황은 여러 가지가 있는데 들어오는 인자가 잘못된 값이라 함수에서 오류가 나는 상황과 정확한 인자가 들어왔더라도 함수가 의존하고 있는 외부의 상태에 따라 결과를 정확히 전달할 수 없는 상황이 대표적이다.
Klesli Composition은 위와 같은 상황을 안전하게 해결하기 위해 존재한다.
f(g(x)) = f(g(x))
f·g에 대해 수학적으로 위의 식은 성립한다.
그러나 프로그래밍에서는 성립하지 않을 수 있다.
좌변의 f(g(x))를 평가하고 난 이후 우변의 f(g(x))를 평가할 때 해당 함수가 온전히 유지되어 있거나 참조하는 x의 값이 좌변의 f(g(x))를 평가하고 난 이후에도 유효한 값인지는 알 수 없기 때문이다.
이러한 상황에서도 특정한 규칙을 만들어 합성을 안전하게 하고 조금 더 수학적이라고 바라볼 수 있게 하는 것이 Klesli Composition이다.
Klesli Composition은 다음과 같은 경우를 말한다.
- g(x)에서 에러가 생기지 않아(= 일반적으로) f(g(x)) = f(g(x))인 경우
- g(x)에서 에러가 생겨 f(g(x)) = g(x)인 경우
즉, f(g(x))의 값과 g(x)의 값이 같을 경우를 Klesli Composition이라고 한다.
✏️ 예제
let users = [
{ id: 1, name: "a" },
{ id: 2, name: "b" },
{ id: 3, name: "c" },
];
const getUserById = (id) => find((u) => u.id == id, users);
const f = ({ name }) => name;
const g = getUserById;
위와 같이 user의 id, name을 가지고 있는 객체를 모아둔 배열 users가 존재하고 id를 통해 user를 찾는 getUserById라는 함수가 있다.
이때 함수 f는 name을 반환하는 함수이고 함수 g는 getUserById이다.
const fg = (id) => f(g(id));
console.log(fg(2));
// output
// b
f·g를 id를 받아 f(g(id))를 리턴하는 함수 fg로 표현했다.
이때 id가 2인 user의 name을 출력하면 b가 출력되고 정상적으로 동작함을 알 수 있다.
const r = fg(2);
console.log(r);
users.pop();
users.pop();
const r2 = fg(2);
console.log(r);
// output
// b
// Uncaught TypeError: Cannot destructure property 'name' of 'undefined' as it is undefined.
다시 fg(2)의 값을 r에 저장했다.
그리고 외부의 요인으로 users에 변화를 준 후 r2를 선언, 다시 r을 출력하도록 했다.
r의 값은 r2랑 상관이 없는데도 불구하고 r2의 선언 과정에서 일어난 에러때문에 r의 값을 출력할 수 없다.
즉, 외부의 상황으로 오류가 나 결과를 제대로 전달할 수 없는 상태이다.
const getUserById = id => find(u => u.id == id, users) || Promise.reject('Error!');
const f = ({ name }) => name;
const g = getUserById;
const fg = (id) => Promise.resolve(id).then(g).then(f);
fg(2).then(console.log);
// output
// b
fg를 Klesli Composition으로 바꾸어보았다.
getUserById 함수에서 id가 유효한 값이라면 정상적으로 find 함수를 실행하고 아니면 Promise의 상태를 실패로 바꾼다.
fg에서는 기존 fg의 정의와 같이 Promise.resolve(id) 이후 함수 g와 함수 f를 순차적으로 실행한다.
const fg = (id) => Promise.resolve(id).then(g).then(f);
users.pop();
users.pop();
fg(2).then(console.log);
console.log(g(2));
// output
// Promise {<rejected>: 'Error!'}
// Promise {<rejected>: 'Error!'}
위와 같이 다시 외부적 요인으로 에러를 일으켰을 때 id가 2인 값은 users 안에 존재하지 않으니 유효한 값이 아니다.
따라서 f(g(x))의 값과 g(x)의 값이 동일한 것을 확인할 수 있다.
'JavaScript' 카테고리의 다른 글
[FP&ES6+] go, pipe, reduce에서 비동기 제어 (0) | 2022.01.08 |
---|---|
[FP&ES6+] 합성 관점에서의 Promise와 Monad (0) | 2022.01.05 |
[FP&ES6+] 일급 값으로의 Promise 활용 (0) | 2022.01.03 |
[FP&ES6+] callback, Promise를 이용한 비동기 동시성 프로그래밍 (0) | 2022.01.02 |
[FP&ES6+] L.flatMap, flatMap (0) | 2021.12.28 |
댓글