-
[Functional] promise와 monad, kleisli compositionprograming/Language 2019. 8. 15. 17:20
안녕하세요, Einere입니다.
(ADblock을 꺼주시면 감사하겠습니다.)
2019/06/25 - [programing/JavaScript] - [Functional] 평가와 일급, 고차함수
2019/07/15 - [programing/JavaScript] - [Functional] 순회와 이터러블
2019/07/15 - [programing/JavaScript] - [Functional] 제네레이터와 이터러블
2019/07/15 - [programing/JavaScript] - [Functional] map, filter, reduce
2019/07/18 - [programing/JavaScript] - [Functional] 코드를 값으로 다루어 표현력 높이기
2019/07/22 - [programing/JavaScript] - [Functional] range,take, 느긋한 L.range, L.take
2019/07/22 - [programing/JavaScript] - [Functional] 지연 평가와 L.map, L.filter
2019/08/12 - [programing/JavaScript] - [Functional] join, find, flatten
오늘은 promise와 monad, kleisli composition에 대해 알아보도록 하겠습니다.
Monad
기능 프로그래밍에서 모나드는 프로그램 로직에 필요한 상용구 코드를 자동화하면서 프로그램을 일반적으로 구성 할 수있는 디자인 패턴입니다. 이 패턴은 모나드가 자체 자료형을 제공함으로써 달성됩니다. 이 자료형은 모나드 내부의 기본 값을 감싸는 절차와 모나딕한 값을 출력하는 함수를 합성하는 절차, 특정 형태의 계산을 표현합니다. 이를 통해, 모나드는 잠재적 undefined값을 다루기(Maybe monad), 유연하고 잘 정의된 리스트에 값을 보관(List monad)과 같은 광범위한 문제를 간략화합니다.
모나드라는 개념과 용어는 범주론에서 유래했으며, 추가적인 기능을 가진 함수자(functor)로서 정의됩니다.
범주론은 모나드 법칙으로 알려진 몇 가지 공식적인 요구 사항을 제공합니다. 이 모나드 법칙은 모나드가 충족해야 하며, 모나드 코드를 확인하는 데 사용될 수 있습니다.
뭔가 어렵네요.
그럼 예제를 보면서 이해를 해봅시다.
const f = val => val + 1; const g = val => val * val; console.log(g(f(1))); // 출력 : 4 console.log(g(f())); // 출력 : NaN
g(f(1))
는 1을 인자로 받아, 적절한 결과인 4를 출력했습니다. 그러나g(f())
는 부적절한 결과인 NaN을 출력했습니다. 따라서, $(g\cdot f)(x)$는 안전하게 합성되지 않은 함수라고 할 수 있습니다.위 코드를 모나드하게 바꾸면 다음과 같습니다.
[1].map(f).map(g).forEach(val => console.log(val)); // 출력 : 1 [].map(f).map(g).forEach(val => console.log(val)); // 출력 : (아무것도 출력하지 않음)
값을 저장하는 컨테이너인 배열을 이용하여, 함수 f를 적용한 뒤, 그 결과를 다시 g에 적용합니다. 최종 출력을 forEach로 순회하면서 터미널에 출력합니다.
일반 코딩의 경우 NaN이 출력되는 반면, 위와 같은 노마드 코딩은 아무것도 출력하지 않습니다. 따라서, 안전하게 함수를 합성할 수 있습니다.
함수형 프로그래밍에 더 가깝게 리팩토링 한다면, 다음과 같이 구현할 수 있습니다.
Array.of(1).map(f).map(g).forEach(val => console.log(val)); // 출력 : 1
Promise와 Monad
함수를 안전하게 합성하기 위해 모나드라는 개념이 도입되었습니다. 모나드 구현체 중, 비동기 상황에서 함수를 안전하게 합성하기 위해 필요한 것이 promise입니다.
위 예제 코드를 promise를 이용한다면 다음과 같이 구현할 수 있습니다.
Promise.resolve(1).then(f).then(g).then(result => console.log(result));
위 코드에서, promise의 특성인 비동기성을 적용하면 다음과 같이 구현할 수 있습니다.
new Promise((res, rej) => { setTimeout(()=> { res(1); }, 2000); }) .then(f) .then(g) .then(result => console.log(result));
2초 후 값을 평가하는 프로미스를 생성하고, 해당 프로미스가 값을 평가한 후 f와 g를 차례대로 적용합니다.
kleisli composition
kleisli composition은 함수 합성 방법중 하나입니다. 오류가 발생할 수 있는 상황에서, 함수 합성을 안전하게 하는 규칙이라고 생각하시면 됩니다.
예를 들어, 함수 f와 g가 있다고 가정합니다. 이 때, 언제 어디서든
g(f(x)) == g(f(x))
이어야 합니다. (수학에서는 자명하겠지만, 프로그래밍에서는 상태에 따라서 결과값이 다를 수 있습니다.) 여기서,f(x)
에서 오류가 발생했다고 가정했을 때,g(f(x)) == f(x)
로 만들어주는 함수 합성 방법이 kleisli composition입니다. 자세히 말하자면, 오류가 발생하여 잘못된 값을 도출한f(x)
의 결과값을 인자로 받은 함수g
또한 동일한 결과값을 출력해야 한다는 뜻입니다. 코드를 예시로 들면 다음과 같습니다.const users = [ {id:1, name:'aa'}, {id:2, name:'bb'}, {id:3, name:'cc'} ]; // 특정 id의 유저를 찾는 함수 const f = id => find(user => user.id === id, users); // 특정 유저 객체의 이름을 얻는 함수 const g = ({name}) => name; // (g*f)(x) 합성 함수 const gf = id => g(f(id)); const result1 = gf(2); users.pop(); users.pop(); const result2 = gf(2); console.log(result1 === result1); // 출력 : true console.log(result1 === result2); // 출력 : error
두 함수 f와 g는 외부 변수인 users에 의존하고 있기 때문에, users를 조작하기 전 결과인 result1과 result2는 서로 다른 값을 가지게 됩니다. 그럼 이 코드를 안전하게 합성해보도록 하겠습니다.
const users = [ {id:1, name:'aa'}, {id:2, name:'bb'}, {id:3, name:'cc'} ]; // 특정 id의 유저를 찾는 함수 const f = id => find(user => user.id === id, users) || Promise.reject('not found'); // 특정 유저 객체의 이름을 얻는 함수 const g = ({name}) => name; // (g*f)(x) 합성 함수 const gf = id => Promise.resolve(id).then(f).then(g).catch(err => err); // case 1 gf(2).then(result => console.log(result)); // 출력 : bb // case 2 users.pop(); users.pop(); gf(2).then(result => console.log(result)); // 출력 : not found
함수
f
에서, 원하는 user를 찾지 못하면 reject 프로미스를 반환합니다. 그리고 합성함수gf
를 promise를 이용해 합성합니다. (catch를 이용해서 에러 핸들링도 해줍니다.) 그 결과, 안전하게 결과를 받는 것을 확인할 수 있습니다.참고
마이클 포거스, 『함수형 자바스크립트 : 새롭고 올바른 자바스크립트 프로그래밍 기법』, 한빛미디어(2014).
유인동, 인프런 - 함수형 프로그래밍과 JavaScript ES6+
'programing > Language' 카테고리의 다른 글
[Express] cookie에 대하여 (0) 2019.09.06 [Functional] 동시성을 가진 C.reduce, C.take, C.map, C.filter (0) 2019.08.21 [Functional] join, find, flatten (0) 2019.08.12 [JS] Date 객체 사용시 주의할 점 (0) 2019.08.04 [JS] Scope, Execution context, Closure (0) 2019.08.03 댓글