코드를 값으로 다루어 표현력 높이기

ddullgi·2022년 6월 12일
0
post-thumbnail

본 포스트는 인프런의 함수형 프로그래밍과 JavaScript ES6+ 강의(링크)를 듣고 정리한 내용입니다.


base array

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

코드를 값으로 다루어 표현력 높이기


go

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

go(
  0,
  (a) => a + 1,
  (a) => a + 10,
  (a) => a + 100,
  console.log
);
//111
  • go라는 함수를 만들어 보았다.

  • 인자의 첫번째 인자를 다음 함수로 전달하고 그 함수의 결과를 다음 함수의 인자로 전달한다.

  • reducef(a, f) => f(a)가 들어가게 된다.

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;
};

f = (a, f) => f(a), acc = 0, iter = [(a) => a + 1, (a) => a + 10, (a) => a + 100, console.log]
//1번 순회
acc = f(0, (a) => a + 1) = 1
f(0, (a) => a + 1) = (0, (a) => a + 1) => f(a) = 0 + 1 = 1
//2번 순회
acc = f(1, (a) => a + 10) = 11
f(1, (a) => a + 10) = (1, (a) => a + 10) => f(a) = 1 + 10 = 11
//3번 순회
acc = f(11, (a) => a + 100) = 111
f(11, (a) => a + 100) = (11, (a) => a + 100) => f(a) = 11 + 100 = 111
//4번 순회
acc = f(111, console.log) //111
f(111, console.log) = (111, console.log) => f(a) = console.log(111) //111
  • 내부에서 순회를 거치며 인자로 들어간 함수가 차례로 실행된다.

pipe

const pipe = (...fs) => {
  return (b) => go(b, ...fs);
};

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

console.log(f(0));
//111
  • pipe라는 함수를 만들어 보았다.

  • 함수들을 인자로 받아서 합성된 함수를 리턴해준다.

  • go함수의 인자로 변수 a함수들을 넣어준다.

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) => {
  return reduce((a, f) => f(a), args);
};

go(
  b,
  (a) => a + 1,
  (a) => a + 10,
  (a) => a + 100,
);

f = (a, f) => f(a), acc = b, iter = [(a) => a + 1, (a) => a + 10, (a) => a + 100]
//1번 순회
acc = f(b, (a) => a + 1) = b + 1
f(b, (a) => a + 1) = (b, (a) => a + 1) => f(a) = b + 1
//2번 순회
acc = f(b + 1, (a) => a + 10) = b + 11
f(b + 1, (a) => a + 10) = (b + 1, (a) => a + 10) => f(a) = b + 1 + 10 = b + 11
//3번 순회
acc = f(b + 11, (a) => a + 100) = b + 111
f(b + 11, (a) => a + 100) = (b + 11, (a) => a + 100) => f(a) = b + 11 + 100 = b + 111


const f = pipe(
  (a) => a + 1,
  (a) => a + 10,
  (a) => a + 100
);
//b + 111
  • 내부에서 순회를 거치며 인자로 들어간 함수가 축약 된다.

초기 인자를 두개이상 전달 받는경우

go(
  add(0, 1),
  (a) => a + 10,
  (a) => a + 100,
  console.log
);
  • go함수의 경우는 초기 인자들을 함수의 내부에 집어 넣어 처리해준다.

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

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

console.log(f(0, 1));
  • pipe함수의 경우는 첫 함수를 여러개의 인자를 받는 형태로 바꿔줘야 한다.
  • 또한 pipe 함수가 받는 첫 함수와 그이후 함수들을 분리해주고, 초기 인자들을 첫 함수로 처리해준다.

go를 이용하여 읽기 좋은 코드 만들기

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

console.log(
    reduce(
        add,
        map(
            (p) => p.price,
            filter((p) => p.price < 20000, products)
        )
    )
);
//30000
  • 앞선 포스트에서 작성했던 이 코드를 go함수를 써서 읽기 좋게 만들어 보자
go(
  products,
  (products) => filter((p) => p.price < 20000, products),
  (products) => map((p) => p.price, products),
  (prices) => reduce(add, prices),
  console.log
);
//30000
  • go함수를 사용했더니 더 읽기 좋은 코드가 되었다.

curry

const curry = (f) => (a, ..._) => _.length ? f(a, ..._) : (..._) => f(a, ..._);
// _의 길이가 있다면 인자가 2개 이상이다.

const mult = curry((a, b) => a * b);
console.log(mult(3)(2)); //6

const mult3 = mult(3);
console.log(mult3(10)); //30
console.log(mult3(5)); //15
console.log(mult3(3)); //9
  • curry라는 함수를 만들어 보았다.
  • 함수를 값으로 다루면서 받아둔 함수를 원하는 시점에 평가 시킨다.
    • 함수를 받아서 일단 함수를 리턴해준다.
      • 리턴된 함수가 실행되었을때 인자가 2개 이상이라면 바로 실행한다.
      • 리턴된 함수가 실행되었을때 인자가 1개라면 함수를 다시 리턴한 후, 그 이후에 받은 인자들을 합쳐서 실행한다.

curry를 이용하여 읽기 좋은 코드 만들기

go(
  products,
  (products) => filter((p) => p.price < 20000, products),
  (products) => map((p) => p.price, products),
  (prices) => reduce(add, prices),
  console.log
);
//30000
  • go함수를 써서 읽기 좋게 만든 코드이다.
//fx.js
const map = curry((f, iter) => {
  let res = [];
  for (const a of iter) {
    res.push(f(a));
  }
  return res;
});

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

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;
});
  • filter reduce map에 전부 curry를 적용해 보았다.
go(
  products,
  (products) => filter((p) => p.price < 20000)(products),
  (products) => map((p) => p.price)(products),
  (prices) => reduce(add)(prices),
  console.log
);
//30000
  • curry를 적용하여 바뀐 코드를 보면 filter reduce map에서 products를 인자로 받아 각각에 함수에 다시 products를 넣어주는 형태이므로 products를 제거해보도록 하겠다.
go(
  products,
  filter((p) => p.price < 20000),
  map((p) => p.price),
  reduce(add),
  console.log
);
//30000


함수 조합으로 함수 만들기

const toal_price = pipe(
  map((p) => p.price),
  reduce(add)
);

go(
  products,
  filter((p) => p.price < 20000),
  toal_price,
  console.log
);
  • 함수를 반복해서 실행하는 부분을 pipe함수를 이용하여 단축하였다.
const base_total_price = predi => pipe(
  filter(predi),
  toal_price  
)

go(
  products,
  base_total_price((p) => p.price < 20000),
  console.log
);
  • 추가로 filter함수 부분까지도 단축시켜 보았다.
profile
프론트엔드개발자를 꿈꾸는 예비 개발자

0개의 댓글