함수형 프로그래밍 - go, pipe, curry

MyeonghoonNam·2021년 8월 10일
1

함수형 프로그래밍

목록 보기
3/10

함수형 프로그래밍 시리즈 내용으로 계속 이어서 내용이 진행되므로 처음 부터 포스팅을 확인해주세요.

직접 정의한 map, filter, reduce 코드를 값으로 다루어 전체적인 코드의 표현력을 높일 수 있다.

이를 위해 주로 사용하는 함수들인 go, pipe, curry 함수를 구현하여 보자.

go

  • go 함수는 인자를 받아 결과를 바로 산출해내는 함수이다.

  • 첫번째 인자는 시작이 되는 값을 받고, 나머지는 함수를 받아 첫번째 인자가 두번째 함수로가 결과를 만들고 그 결과가 또 세번째 함수로가 그 결과가 만들어지는 과정이 마지막까지 계속된다.

const go = (...args) => reduce((a, f) => f(a), args); 

go(
  add(0, 1),
  a => a + 10,
  a => a + 100,
  console.log
);

pipe

  • pipe 함수는 go와 비슷하지만 값을 리턴하는 것이 아닌, 함수를 리턴하는 함수이다.

  • go는 인자로 받은 함수들을 모두 실행시켜 결과에 해당하는 값을 리턴하지만, pipe는 인자로 받은 함수들을 모두 합쳐 합성된 함수를 리턴한다.

const pipe = (f, ...fs) => (...as) => go(f(...as), ...fs);

const f = pipe(
  (a, b) => a + b,
  a => a + 10,
  a => a + 100
)

console.log(f(0, 1));

curry

curry 함수는 받아둔 함수를 원하는 시점에 평가하도록 하는 함수이다. 여러개의 인자가 필요한데 하나의 인자만 받았을 경우 다음 인자를 받을 수 있도록 기다리는 함수를 만드는 것이다.

const curry = f => 
  (a, ..._) => _.length ? f(a, ..._) : (..._) => f(a, ..._);

아직 감이 잘 안잡히니 currying에 대해 먼저 이해하자.

currying ?

커링은 여러 개의 인자를 가진 함수를 호출할 경우, 파라미터의 수보다 적은 수의 파라미터를 인자로 받으면 누락된 파라미터를 인자로 받도록 하는 것이다.

즉 커링은 함수 하나가 n개의 인자를 받는 과정을 n개의 함수로 각각의 인자를 받도록 하는 것이다. 부분적으로 적용된 함수를 체인으로 계속 생성해 결과적으로 값을 처리하도록 하는 것이다.

간단하게 말하면 함수를 부르는 함수를 만들어 인자를 각 함수가 각각 받는 것이다. func(1, 2, 3)이 아닌, func(1)(2)(3) 이런 방식으로 말이다.

이러한 함수를 생성할 때는 당연히 함수를 반환하는 클로저 패턴이 사용되는데, 때문에 let func1 = func(1)과 같이 생성해두고 func1(3)(4), func(5)(6)과 같이 변형하면 첫 번째 인자는 고정된 상태에서 다른 인자를 받을 수 있다.

function func(a){
    return function(b){
        return function(c){
            console.log(a, b, c);
        }
    }
}
 
func(1)(2)(3); // 1 2 3
 
let func1 = func(1);
func1(3)(4); // 1 3 4
func1(5)(6); // 1 5 6

커링 작업을 map, filter, reduce에 적용하기

const curry = f => 
  (a, ..._) => _.length ? f(a, ..._) : (..._) => f(a, ..._);

const map = curry((f, iter) => {
  const response = [];

  for(let value of iter) {
    response.push(f(value));
  }

  return response;
})

const filter = curry((f, iter) => {
  const response = [];
  for(const i of iter) {
    if(f(i)) response.push(i);
  }

  return response;
})

const reduce = curry((f, acc, iter) => {
  if(!iter) {
    iter = acc[Symbol.iterator]();
    acc = iter.next().value;
  }

  for(const i of iter) {
    acc = f(acc, i);
  }

  return acc;
})

이와 같이 구현한 커링메소드들로 동일 결과를 출력하는 코드의 축약을 이룰 수 있다.

// 함수의 코드 변천 과정 : 출력코드는 동일하다.
console.log(
  reduce(
    add,
    map(p => p.price,
      filter(p => p.price < 20000, products))));

// go 함수를 통한 축약
go(
  products,
  products => filter(p => p.price < 20000, products),
  products => map(p => p.price, products),
  prices => reduce(add, prices),
  console.log
);

// currying을 통한 축약
go(
  products,
  filter(p => p.price < 20000),
  map(p => p.price),
  reduce(add),
  console.log
);

이것만으로도 많은 축약이 이루어 졌지만 아직 코드의 중복이 존재하고 이를 pipe 함수를 활용하여 함수+함수 조합으로 새로운 함수 표현이 가능하다.

// 함수 조합으로 함수 만들기
// pipe 함수 활용
const total_price = pipe(
  map(p => p.price),
  reduce(add)
);

const base_total_price = predi => pipe(
  filter(predi),
  total_price
);

go(
  products,
  base_total_price(p => p.price < 20000),
  console.log
  );
  
go(
  products,
  base_total_price(p => p.price >= 20000),
  console.log
);



참고자료

profile
꾸준히 성장하는 개발자를 목표로 합니다.

0개의 댓글