이전 포스팅 참고
- 함수의 연속적인 실행
• 체인 방식
체인 방식은 메서드를 연속적으로 실행하면서 객체의 상태를 변경해 나가는 기법입니다.
이 방식은 표현력도 좋고 실행 순서를 눈으로 따라가기에도 좋고, 여러 장점을 갖고 있습니다.
제가 실제 작업시 사용했던 예시 코드를 간단히 보도록 하겠습니다.
const itemCost = function(cost) {
let modifiedCost = cost;
const modify = function(fn, opts) {
modifiedCost = fn(modifiedCost, opts);
return this;
};
const get = function() {
return modifiedCost;
};
return { modify, get };
};
const xxxCost = itemCost(1000)
.modify(applyExtraChargeRate, { ... })
.modify(discountRate, { ... })
.get();
아이템에 대한 가격을 담고 있는 함수 내부를 클로저로 만들었고 (modify, get 내부 함수에서 modifiedCost 에 접근), modify 함수는 this 를 반환하도록 해 modify 를 계속 체이닝해서 가격을 변경하는 함수를 계속 적용할 수 있도록 만들었습니다.
이러한 체인 방식의 단점이라고 하면 (객체에서도 마찬가지) 갖고 있는 메서드만 이용할 수 있기 때문에 관련 없는 로직, 함수를 중간에 섞어 사용하기에는 어렵다는 점입니다. 위 예시에선 itemCost -> modify -> get 함수를 연속적으로 실행해 완성된 결과를 바로 얻어낼 수 있지만, 상황에 따라선 이러한 체인을 끊어야 하는 경우도 있고 그럴 경우 체인 방식의 이점을 많이 잃어버리게 됩니다.
위 방식대로면 체인 방식을 사용하기 위해서 반드시 함수가 this 를 반환하도록 해야 하고, 만약 객체의 경우라면 반드시 객체 생성 단계를 거쳐야 합니다. 또한 this 등의 상태와 흐름에 의존하기 때문에 아무 때나 사용 가능하지 않다는 점이 있습니다.
• _.compose
이전 포스팅들에서 계속 사용해왔던 Underscore.js 의 _.compose 함수는 어느정도 사용법만 파악하면 바로 코딩 및 설계가 가능합니다. 비교적 허들이 낮은 편이지만, 사용하면서 아쉬운 점이라고 한다면 함수 실행 순서가 오른쪽에서 왼쪽으로 되어 있기 때문에 읽기에 약간 불편하다는 것입니다.
• _.pipeline
Partial.js 에 있는 _.pipe 함수를 보기 전에, 먼저 함수형 자바스크립트 책의 저자인 마이클 포거스가 만든 _.pipeline 함수를 보도록 하겠습니다. (참고 링크)
_.pipeline = function() {
var funs = arguments; // functions
return function(seed) {
return _.reduce(funs,
function(l, r) { return r(l); },
seed);
};
};
_.pipeline 함수는 _.compose 의 장점은 그대로 가지고 있습니다. 다른 점은 함수 실행 방향이 왼쪽에서부터 오른쪽입니다.
arguments 에는 함수들이 들어오게 되고, 그 내부는 _.reduce 를 이용해 만들어져 첫 인자인 seed 를 전달해서 r(l) 을 통해 모든 함수를 연속적으로 실행합니다.
즉 인자로 받은 함수들을 실행하면서 함수의 실행 결과를 다음 함수의 인자로 계속 넘겨주다가, 마지막 함수의 결과를 반환하는 함수를 반환하는 고차 함수 입니다.
const cal = _.pipeline(
function(a) { return a / 2; },
function(a) { return a * a; });
const test1 = cal(6);
console.info(test1); // 9
const test2 = cal(10);
console.info(test2); // 25
_.pipeline 을 통해 만들어진 함수에 6 인자를 넘기게 되면, 첫번째 함수부터 차례대로 실행하면서 그 결과는 다음 함수로 넘어가게 됩니다.
6 -> 3 -> 9 가 되면서 9 라는 결과값을 얻어낼 수 있습니다.
결국 _.pipeline 은 작은 함수들을 모아 큰 함수를 만드는 함수고, 이를 통해 함수를 조립할 수 있으며 클래스를 대체할 수도 있습니다.
위에서 봤던 체인 방식과 다르게 다른 함수를 중간에 끼워 넣기에도 용이합니다. _.partial 과 조합해 사용하면 로직을 단순하게 하며, 서로 다른 기능을 하지만 조건문 없이 처리할 수 있습니다.
함수형 자바스크립트의 장점이기도 하지만, _.pipeline 을 사용하기 위해 만든 작은 함수들은 작성 및 테스트가 쉽고 앞뒤로 받을 인자와 결과만을 생각하면서 문제를 작게 만들기 때문에 문제 해결도 쉽습니다. 각 함수가 서로의 상태에 의존하지 않는 방식으로 조합되다보니 신경 쓸 부수효과도 줄어들게 됩니다.
실제로 실무에서 다양한 곳에 적용을 해봐야 알겠지만, _.pipeline 을 잘 활용한다면 다시 만들기 쉬우면서 이후에도 성능 개선하기에 편한 코드를 작성할 수 있을 것 같습니다.
물론 여기서도 마이클 포거스의 _.pipeline 에 아쉬운 점이 있다고 한다면 우선 인자를 하나만 받을 수 있다는 점입니다.
이 의미는 결국 _.pipeline 에 들어갈 함수들 중 인자를 두 개 이상 받는 함수들은 들어갈 수 없다는 것이죠. 약간 트리키하게 인자는 하나만 받고 args[0], args[1] 이런식으로 사용은 할 수 있겠지만 너무 _.pipeline 에만 맞춘 함수가 됩니다. (이렇게 하면 재사용성이 떨어지죠)
자바스크립트의 인자는 가변적이고 함수형 프로그래밍에선 순수 함수를 많이 사용할수록, 인자를 적극 활용할수록 강력해지는데 더 강력해질 수 없도록 구속하는 느낌입니다. Go 언어에선 Multiple Results 라는 개념이 있는데, 함수의 결과값을 여러개로 반환하는 개념입니다. 여러개를 반환한다면 여러 인자를 받는 함수도 파이프라인에 사용할 수 있겠죠. 자바스크립트에선 이런 기능 자체는 없으므로 비슷하게 흉내는 낼 수 있겠지만, 아쉽긴 하네요.
- _.pipe (Partial.js)
위에서 봤던 _.pipeline 도 충분히 강력합니다. 다만 Multiple Results 에 대한 부분과 파이프라인 내부에서 this 를 사용할 수 없다는 아쉬운 점을, Partial.js 의 _.pipe 함수로 해결할 수 있습니다.
_.pipe 함수는 이 두 가지를 모두 지원합니다. 따라서 인자를 2개 이상 필요로 하는 함수도 같이 사용할 수 있고, this 를 사용하는 라이브러리들과의 협업 또한 가능합니다.
const _ = require('partial-js');
_.pipe(
function() {
return this.a;
},
console.info,
function() {
this.b = 2;
return this;
},
console.info
).call({ a: 1 });
// 1
// { a: 1, b: 2 }
const f1 = _.pipe(
funtion(a, b) {
return a + b;
},
function(a) {
return a * a;
},
console.info
);
f1(2, 3); // 25
처음의 예시처럼 _.pipe 정의 후 call 을 통해 실행할 수도 있고, 두번째 예시처럼 실행 준비가 된 함수를 만들어 두고 인자를 넘겨 실행할 수 있습니다. 첫 예시에서 call 에 넘긴 { a: 1 } 이 파이프라인 내부의 this 가 되는 것입니다.
• _.go
_.go 는 _.pipe 의 즉시 실행 버전입니다. 위에서 본 것처럼 _.pipe 에서는 실행 준비가 된 함수를 미리 만들어 두고 인자를 나중에 넘기고 있는데, _.go 는 첫번째 들어가는 인자가 그 다음 함수에서 사용할 인자가 됩니다. 이후 진행되는 방식은 _.pipe 와 동일합니다.
몇 가지 예시를 보겠습니다.
_.go(10,
function(a) { return a * 10 },
function(a) { return a - 50 },
function(a) { return a + 10 },
console.info); // 60
첫 번째 인자로 들어온 10 이 두 번째 인자로 들어온 함수에서 사용할 인자가 되고, 그 결과값은 다음 함수에서 사용할 인자가 됩니다.
따라서 각 함수들이 순서대로 실행되어 10 -> 100 -> 50 -> 60 의 결과값을 얻게 됩니다.
_.go(_.mr(2, 3),
function(a, b) {
return a + b;
},
function(a) {
return a * a;
},
console.info); // 25
첫 번째 인자에 하나의 값만 넣을 수 있으니 여러 인자는 어떻게 넘길까 에 대한 부분(Multiple Results) 은 _.mr 함수를 사용해서 해결할 수 있습니다. _.mr 로 인자를 감싸서 넘기면, 다음 함수는 인자를 여러 개로 펼쳐서 받게 됩니다.
여기까지 함수의 연속적인 실행 방법에 대해 살펴봤습니다.
체인 방식은 준비된 메서드나 규격에 맞춰서 사용해야 하는 제약 사항이 있지만, _.pipline 혹은 _.pipe 와 같은 파이프라인은 아무 함수나 사용할 수 있어서 더 유연하고, 함수적입니다. 클로저나 엘릭서 같은 함수형 언어들도 체인 방식이 아닌 함수 중첩과 파이프라인을 사용한다고 합니다. 나중에 기회가 되면 저 언어들에 대해 맛보는 시간도 가져야겠습니다.
'Backend > 함수형 자바스크립트' 카테고리의 다른 글
Partial.js (2) (0) | 2021.10.02 |
---|---|
Partial.js (1) (0) | 2021.09.25 |
함수 조립 (1) (0) | 2021.09.11 |
Underscore.js - _.reduce (0) | 2021.09.05 |
Underscore.js - _.reject & _.find (0) | 2021.08.29 |