JavaScript/함수

[JavaScript]pipe 함수와 compose 함수 사용 방법

DevStory 2021. 12. 13.

JavaScript 함수형 프로그래밍에서 유용하게 사용되는 pipe 함수와 compose 함수를 소개합니다.

 


pipe(), compose() 함수를 사용하지 않는 경우

pipe 함수는 n개의 함수를 결합합니다. pipe 함수는 왼쪽에서 오른쪽으로 진행되며, 최종적으로 마지막 함수를 호출합니다.

 

다음은 객체의 name 속성을 반환하는 함수입니다.

getPersonName = ( person ) => person.name;

getPersonName({name: 'Hong Gil Dong'});
// 'Hong Gil Dong'

 

그리고 문자열을 대문자로 변환하는 함수를 작성합니다.

uppercase = ( strValue ) => strValue.toUpperCase()

uppercase('Hong Gil Dong');
// 'HONG GIL DONG'

 

person 객체의 name 속성 값을 가져와서 대문자로 변경하는 경우 다음과 같이 코드를 작성할 수 있습니다.

personName = getPersonName({ name: 'Hong Gil Dong' });
uppercase(personeName);
// 'HONG GIL DONG'

 

person 객체의 name 속성 값을 변수에 할당하지 않고 다음과 같이 바로 대문자로 변경할 수 있습니다.

uppercase(getPersonName({ name: 'Hong Gil Dong' }));
// 'HONG GIL DONG'

 

변수를 사용하는 방법보다 코드가 심플해졌지만, 위 코드처럼 계속해서 중첩되는 경우 코드가 이해하기 어려워집니다. 여기서 문자열의 처음 4자리를 가져오는 함수를 추가한다면 다음과 같이 코드를 작성할 수 있습니다.

get4Characters = ( strValue ) => strValue.substring(0, 4)

get4Characters('Hong Gil Dong');
// 'Hong'

 

person 객체의 name 속성 값을 대문자로 변경하고 문자열 처음 4자리를 가져오는 코드를 작성해봅니다.

get4Characters(uppercase(getPersonName({ name: 'Hong Gil Dong' })));
// 'HONG'

이렇게 코드가 계속해서 중첩되는 경우 코드가 상당히 난잡해집니다.


pipe() 함수

pipe() 함수는 모든 함수를 합치며, 왼쪽에서 오른쪽으로 진행됩니다.

 

위에서 작성한 코드는 pipe() 함수를 사용하여 다음과 같이 작성할 수 있습니다.

pipe(getPersonName, uppercase, get4Characters)({ name: 'Hong Gil Dong' }));
// 'HONG'

pipe는 JavaScript에 존재하는 함수가 아닌 함수의 패턴이며, 호출해야 하는 함수의 수가 많아질 때 사용할 수 있는 패턴입니다. pipe() 함수는 다음과 같은 형태로 작성할 수 있습니다.

const pipe = (...functions) => dataToConvert => functions.reduce((acc, fn) => fn(acc), dataToConvert);

 

위에서 구현한 pipe() 함수는 Rest 파라미터(나머지 매개변수)를 사용하여 n개의 함수를 연결할 수 있습니다.

 

pipe() 함수의 동작 과정을 살펴보기 위해 다음과 같이 pipe() 함수에 debugger문을 추가합니다.

const pipe = (...functions) => (value) => {
  debugger;
  
  return functions.reduce((currentValue, currentFunc) => { 
    debugger;
    
    return currentFunc(currentValue);
  }, value)
}

 

다음 코드를 실행합니다.

const pipe = (...functions) => (value) => {
  debugger;
  
  return functions.reduce((currentValue, currentFunc) => { 
    debugger;
    
    return currentFunc(currentValue);
  }, value)
}

getPersonName = ( person ) => person.name;
uppercase = ( strValue ) => strValue.toUpperCase();
get4Characters = ( strValue ) => strValue.substring(0, 4);

pipe(getPersonName, uppercase, get4Characters)({ name: 'Hong Gil Dong' });

첫 번째 debugger

첫 번째 debugger가 동작할 때, consolefunctionsvalue를 입력합니다. functions는 3가지 함수 배열이며, value{name : 'Hong Gil Dong'}입니다. 

 

두 번째 debugger

두 번째 debuggerreduce() 함수 내부에서 실행하며, reduce() 함수 내부는 currentFunccurrentValue를 전달하고 값이 반환됩니다. currentFunc(currentValue)의 결과는 person 객체의 name 속성 값이 반환됩니다. 

 

세 번째 debugger

세 번째 debugger에서 currentValue는 이전에 반환된 값인 'Hong Gil Dong'입니다. currentFuncuppercase() 함수입니다. 그래서 'Hong Gil Dong'을 대문자로 변환합니다.

 

네 번째 debugger

네 번째 debugger에서는 문자열 처음 4자리를 반환하며, pipe() 함수가 종료됩니다.


compose() 함수

compose() 함수는 pipe() 함수와 진행 방향이 다르다는 차이점이 존재합니다.

compose(get4Characters, uppercase, getPersonName)({ name: 'Hong Gil Dong' }));
// 'HONG'

composepipe와 마찬가지로 JavaScript에 존재하는 함수가 아닌 함수 패턴이므로 직접 구현해야 합니다. compose() 함수는 reduceRight() 함수를 사용하여 다음과 같은 형태로 작성합니다.

const pipe = (...functions) => dataToConvert => functions.reduceRight((acc, fn) => fn(acc), dataToConvert);

 

반응형

댓글