21. 8. 10(화) TIL(go, pipe, curry)

배준형·2021년 8월 10일
0

TIL

목록 보기
8/21
post-custom-banner

Javascript Go / Pipe / Curry

📌 map, filter, reduce

map, filter, reduce

이터러블 프로토콜을 따르는 iterable 객체와 함수를 이용하여 map, filter, reduce 구현

const map = (func, iterable) => {
  const result = [];
  for (const a of iterable) result.push(func(a));
  return result;
}

const filter = (func, iterable) => {
  const result = [];
  for (const a of iterable) {
    if (func(a)) result.push(a);
  }
  return result;
}

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

위 처럼 이터러블 프로토콜을 따르는 이터러블 객체를 이용하여 map, filter, reduce 함수를 작성하면 javascript 내의 모든 것에 대해 함수를 사용할 수 있게 된다.

예를 들어,

// map 고차함수를 이용한 방법
console.log(document.querySelectorAll('*').map(el => el.nodeName)); // Error

// 위에서 작성한 이터러블 객체를 인자로 받는 map 함수를 이용한 방법
console.log(map(el => el.nodeName, document.querySelectorAll('*'))); // 배열 리턴

위와 같은 상황에서 차이가 발생하는데, 이는 document.querySelector가 array를 상속받은 것이 아니어서 map 함수가 구현되어 있지 않기 때문에 array map 고차함수를 사용할 수 없지만, 이터러블 프로토콜을 따르고 있기 때문에 작성한 map 함수는 구현이 가능하다.



📌 go

go 함수 : 함수들과 인자들을 전달해서 즉시 어떤 값을 평가하는데 사용된다.

첫번째 인자로 값을 넣고 그 외 나머지 인자에는 함수들을 받아 값을 다음 함수로 넘기면서 차례대로 함수를 실행한다.

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

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

go함수는 함수들을 차례로 실행하면서 하나의 결과로 축약하는 과정이기 때문에 reduce 함수를 활용하여 구현한다.



📌 pipe

pipe 함수 : 함수들이 나열되어 있는 합성된 함수를 만드는 함수이다.
여러 함수들을 차례대로 합쳐서 하나의 함수를 리턴한다.

첫 번째 함수의 인자가 1개 일 때

const pipe = (...fs) => (a) => go(a, ...fs);

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

console.log(f(0)); // 111

첫 번째 함수의 인자가 2개 이상 일 때

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

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

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

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

pipe함수는 함수들을 하나의 함수로 합처서 리턴하고, 리턴된 함수는 인자를 받아 함수들을 적용하며 값을 리턴한다.



📌 curry

curry 함수 : 함수를 값으로 다루면서, 받아둔 함수를 원하는 시점에 평가시키는 함수이다.
함수를 받아 함수를 리턴하는데, 리턴된 함수의 인자가 여러개일 경우에는 받아둔 함수를 즉시 실행하고, 인자가 여러 개가 아닐 경우에는 함수를 다시 리턴하는데, 리턴된 함수가 인자를 받았을 때 앞서 받아둔 인자와 이후에 받은 인자를 합쳐서 실행하게 된다.

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

위에서부터 순서대로 map+filter+reduce 사용, go 사용, pipe 사용, curry 사용하면서 코드의 변화는 다음과 같다.
(코드는 products라는 객체 내에 price 항목의 key, value가 존재하고, 이를 map, filter, reduce를 이용하여 일정 금액 이상(또는 이하) 품목의 가격을 모두 더한 값을 구하는 과정이다.)

// 1. map + filter + reduce 함수 이용.
console.log(reduce((a, b) => a + b, 0, filter(el => el < 20000, map(p => p.price, products))));

// 2. go 함수 이용.
go(
  products,
  products => filter(p => p.price < 20000, products),
  products => map(p => p.price, products),
  prices => reduce((a, b) => a + b, prices),
  console.log);

// 3. pipe 함수 이용.
const f = pipe(
  products => filter(p => p.price < 20000, products),
  products => map(p => p.price, products),
  prices => reduce((a, b) => a + b, prices));

console.log(f(products));

// 4. curry 함수 이용.
const curry = f =>
(a, ..._) => _.length ? f(a, ..._) : (..._) => f(a, ..._);

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

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

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

go(
  products,
  filter(p => p.price >= 20000),
  map(p => p.price),
  reduce((a, b) => a + b),
  console.log
);

위 코드들은 전부 같은 값을 반환한다.



📌 배운점

iterable, iterator에 이어 go, pipe, curry 함수에 대해 학습하였다. 기존에 고차함수인 map, filter, reduce 등의 함수들만 사용할 줄 알아도 충분하다고 생각했으나 위에 예시에 든 것 처럼 map 고차함수를 사용하고 싶어도 배열을 상속받지 않은 항목들에 대해서는 사용할 수 없으니 특정 상황에서 고차함수는 제한적일 수 밖에 없다.
오늘 배운 go, pipe, curry 함수에 대해 완전히 이해하고 사용할 수 있게 되면 더 다양한 상황에 응용이 가능해지고 복잡한 구조도 간결하게 바꿀 수 있어 꼭 필요한 내용이라고 생각한다.


📌 앞으로

1일 전 iterable, iterator, generator 등에 대해 배울 때 긴가민가 하면서 잘 이해가 가지 않았다가 하루가 지나니 조금씩 이해가 되었는데, 오늘 배운 go, pipe, curry 함수에 대해서도 완전한 이해는 되지 않는다. 오늘 배운 내용들도 시간이 지남에 따라 반복적으로 사용하면서 점점 익숙해질거라 믿는다. 그래서 오늘 이해가 가지 않더라도 계속 코드를 써보고 하나씩 출력해보면서 조금씩 학습해나갈 계획이다.

처음이라서 이해가 안가고 어렵게 느껴지는 부분들이 있지만, 한편으로는 작성된 코드들이 마냥 어렵게만 느껴지지는 않는다. 그만큼 이해가 되었을 때 위 내용들을 적절하게 사용 가능하게 되면서 다양한 상황에 응용이 가능해질 것 같다.


📌 참고한 사이트

https://velog.io/@younoah/functional-js-%ED%95%A8%EC%88%98%EC%9D%98-%EC%A4%91%EC%B2%A9-go-pip-curry
https://n-log.tistory.com/38

profile
프론트엔드 개발자 배준형입니다.
post-custom-banner

0개의 댓글