[자바스크립트] 함수 결합

[자바스크립트] 함수 결합

💡 tl;dr


  • 함수 결합(Function Composition)의 정의
  • 예시 및 활용



함수 결합이란


  • 두 개 이상의 함수를 결합하여 새 함수를 생성하는 프로세스
  • 함수를 결합하는 것은 데이터가 흐르기 위해 파이프를 까는 것과 같음

예를 들어, 는 자바스크립트에서 f(g(x))와 같고, 이는 아래처럼 안에서 밖으로 순서대로 계산된다.

  1.  
  2.  
  3.  



함수 결합의 활용


아래처럼 chain으로 구성된 간단한 코드가 있다.

1
2
3
4
5
const toSlug = input => encodeURIComponent(
input.split(' ')
.map(str => str.toLowerCase())
.join('-')
);


이를 함수 결합 형태로 다음과 같이 바꿀 수 있다.

1
2
3
4
5
6
7
8
9
10
11
const toSlug = input => encodeURIComponent(
join('-')(
map(toLowerCase)(
split(' ')(
input
)
)
)
);

console.log(toSlug('JS Cheerleader')); // 'js-cheerleader'

첫 함수보다 가독성이 떨어져 보이지만, 결합의 방향성은 확연히 보인다.



.reduceRight()


Javascript에는 이를 개선할 수 있는 Array 메서드 .reduce()가 있다.

그러나 .reduce()는 값의 전달 방향이 (왼쪽에서 오른쪽)으로, 예제 toSlug의 형태와는 정 반대의 방향성을 보인다. 이때 사용할 수 있는 함수가 .reduceRight()다. .reduce()와 마찬가지로 reducer 함수와 초기값 x를 사용하고, 배열 함수를 (오른쪽에서 왼쪽)으로 반복하며 누적된 값(v)에 차례로 적용한다.

1
const compose = (...fns) => x => fns.reduceRight((v, f) => f(v), x);


이를 적용하면 중첩 없이 위 함수 결합을 다시 작성할 수 있다.

1
2
3
4
5
6
7
const toSlug = compose(
encodeURIComponent,
join('-'),
map(toLowerCase),
split(' ')
);
console.log(toSlug('JS Cheerleader')); // 'js-cheerleader'



.reduce()


마찬가지로 .reduce()를 사용하면 (왼쪽에서 오른쪽)으로 순서대로 적용되는 함수의 흐름을 구현할 수 있다. 이를 pipe라고 표현한다.

1
2
3
4
5
6
7
8
const pipe = (...fns) => x => fns.reduce((v, f) => f(v), x);

const fn1 = s => s.toLowerCase();
const fn2 = s => s.split('').reverse().join('');
const fn3 = s => s + '!'

const newFunc = pipe(fn1, fn2, fn3);
const result = newFunc('Time'); // emit!

사용 방법 또한 앞선 compose 함수와 똑같다. 단지 방향성이 바뀌었을 뿐이다.


이를 적용하여 toSlug를 다음과 같이 수정할 수 있다.

1
2
3
4
5
6
7
const toSlug = pipe(
split(' '),
map(toLowerCase),
join('-'),
encodeURIComponent
);
console.log(toSlug('JS Cheerleader')); // 'js-cheerleader'

함수의 진행 방향이 정확히 반대가 되었다.



trace


데이터가 흐르는 파이프를 구현해놨다면, 이를 검증할 수 있는 trace를 사용할 수 있다.

trace란 파이프 사이사이에서 데이터의 흐름이 올바른지 확인할 수 있는 전략으로, 일반적으로 다음과 같이 구현한다.

1
2
3
4
const trace = curry((label, x) => {
console.log(`== ${ label }: ${ x }`);
return x;
});


사용 방법은 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
const toSlug = pipe(
trace('input'),
split(' '),
map(toLowerCase),
trace('after map'),
join('-'),
encodeURIComponent
);

console.log(toSlug('JS Cheerleader'));
// '== input: JS Cheerleader'
// '== after map: js,cheerleader'
// 'js-cheerleader'



tap


tap이란 파이프를 통해 흐르는 데이터에 몇 가지 작업을 수행할 수 있도록하는 기법으로, trace()는 일반적인 tap()의 한 형태일 뿐이다.

다음과 같이 tap()을 작성할 수 있다.

1
2
3
4
const tap = curry((fn, x) => {
fn(x);
return x;
});


이제 tap을 이용해 trace를 구현할 수 있다.

1
2
3
const trace = label => {
return tap(x => console.log(`== ${ label }: ${ x }`));
};


이러한 예제들을 통해 함수형 프로그래밍이 무엇인지, 어떻게 함수 결합을 활용하여 더 적은 boilerplate로 더 읽기 쉬운 프로그램을 작성하는지 이해할 수 있다.



참고


해당 글은 Master the JavaScript Interview: What is Function Composition?을 번역 및 수정한 글입니다.


[자바스크립트] 함수 결합

https://sklubmk.github.io/2021/08/02/c61ae76ac2af/

Author

Jinki Kim

Posted on

2021-08-02

Updated on

2021-08-02

Licensed under

댓글