중첩 함수 표현력 높이기(go, pipe, currying)

boyeonJ·2023년 6월 25일
0
post-thumbnail

지난번 Array의 reduce, filter, map함수를 interable용으로 만들어보며 함수를 값으로 다루는 방법에 대해 알아보았다. 작성한 함수는 아래와 같이 사용할 수 있었다.

const products = [
  {name: '반팔티', price: 15000},
  {name: '긴팔티', price: 20000},
  {name: '핸드폰케이스', price: 15000},
  {name: '후드티', price: 30000},
  {name: '바지', price: 25000}
];

const map = (f, iter) => {
  let res = [];
  for (const a of iter) {
    res.push(f(a));
  }
  return res;
};

const filter = (f, iter) => {
  let res = [];
  for (const a of iter) {
    if (f(a)) res.push(a);
  }
  return res;
};

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

const add = (a, b) => a + b;

log(
  reduce(
    add,
    map(p => p.price,
        filter(p => p.price < 20000, products))));

최종적으로 map, filter, reduce를 모두 활용한 코드는 아래와 같이 여러가지 함수가 중첩된 모습이다. 이 코드를 좀 더 간결하게 표현하여 코드를 좀 더 읽기 쉽고 표현력이 좋게 만들어 보도록 하자!!

log(
  reduce(
    add,
    map(p => p.price,
        filter(p => p.price < 20000, products))));

go 함수

go 함수는 reduce를 활용하여 첫번째 인수부터 오른쪽으로 차례차례 인수(함수 리턴값)를 전달하며 중첩 함수를 실행시키는 함수이다.

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


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

go(products, 
   products => filter(p => p.price < 20000, products),
   products => map(p => p.price,products),
   products => reduce(add, price),
   log);

go(products, 
   filter(p => p.price < 20000),
   map(p => p.price),
   reduce(add),
   log);

위의 순서를 직접 작성해보면

  1. products => filter(p => p.price < 20000, products) 가 아래의 a
  2. productsacc이다.
  3. 그리고 f는 (a,f)=>f(a) 이므로
  4. products => filter(p => p.price < 20000, products)products를 파라미터로 넘기는 것!
  5. 그리고 이 return 값이 다시 acc로 저장된다!


pipe

pipe함수는 go를 활용한 함수

const pipe = (...fs) => (a) => go(a, ...fs);
const f = pipe(
    a=> a+1,
    a => a+10,
    a => a+100
); 
f(0)// 111

//인자 여러개
const pipe = (f,...fs) => (...as) => go(f(...as), ...fs);
const f = pipe(
    (a,b)=> a+b,
    a => a+10,
    a => a+100
); 
f(0,1) // 111


pipe와 go의 차이점

go는 함수를 즉시 실행하여 결과값을 반환해 다음 함수로 전달하는데에 반해, pipe는 함수 자체를 반환하여 최종적으로 함수 리스트를 합성해서 합성된 함수를 가지고 로직은 수행.

pipe는 go와 유사한 로직이지만 즉시 실행 되는 부분만 변환되어 지연실행이 된다.


Currying

함수형 프로그래밍에서 사용되는 기법으로, 여러 개의 인자를 받는 함수를 단일 인자를 받는 함수의 연속으로 변환하는 것을 말합니다. 이를 통해 함수를 부분적으로 적용하고 재사용할 수 있는 장점이 있습니다.

커링 함수는 원하는 시점에 평가할때 좋은 방법입니다. 대표적인 예로 아래의 코드는 만약 인자값이(나머지 인자값)이 존재하면 함수를 즉시 실행하고, 아니라면 함수를 반환하여 가지고 있다가 나중에 인자값을 받으면 실행하는 함수입니다.

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

const mult = curry((a, b) => a * b);
log(mult);//(a, ..._) => _.length ? f(a, ..._) : (..._) => f(a, ..._)
log(mult(1)); //(..._) => f(a, ..._)
log(mult(2,3)); // 6
log(mult(2)(3)); //6

go 함수를 pipe와 curring을 이용하여 모듈화 하기


go(
    books,
    filter(p => p.price >= 30000),
    map(p
 => p.price),
    reduce(add),
    log
)

go(
    books,
    filter(p => p.price < 30000),
    map(p => p.price),
    reduce(add),
    log
)

//1차
const total_price = pipe(
    map(p => p.price),
    reduce(add)
);
go(
    books,
    filter(p => p.price < 30000),
    total_price,
    log
)

//2차
const total_price = pipe(
    map(p => p.price),
    reduce(add)
);
const base_total_price = predi => pipe(
    filter(predi),
    total_price
);
go(
    books,
    base_total_price(b=>b.price >=30000),
    log
)

go(
    books,
    base_total_price(b=>b.price <30000),
    log
)

0개의 댓글