본문 바로가기

Web[웹]/ES6+ 함수형 언어

[JS: ES6+] 장바구니 예제로 코드를 줄이고 HTML로 표현해보기

 

#1 go, pipe, curry를 활용한 코드 줄이기 실습

 

장바구니 예제 실습

 

앞에서 배웠던 [go, pipe, curry: https://opentogether.tistory.com/70?category=807380]를 기반으로 코드를 줄여보는 것을 한번 더 해볼것이다.

 

/*외부 파일: fx.js*/
const go = (...args) => args.reduce((a, f) => { return f(a); });

const pipe = (...funcs) => arg => funcs.reduce((a, f) => f(a), arg);

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

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

 

아래 코드는 이전 코드에서 quantity(수량)이 추가된 코드이다.

 

const products = [
   { name: '반팔티', price: 15000, quantity: 1 },
   { name: '긴팔티', price: 20000, quantity: 2 },
   { name: '후드티', price: 40000, quantity: 3 },
   { name: '긴바지', price: 30000, quantity: 4 },
   { name: '반바지', price: 25000, quantity: 5 },
];

 

먼저, 여기서 go를 활용해 총 수량을 단계적으로 뽑아보자.

 

go(products,
   map(p => p.quantity),
   console.log); // [1, 2, 3, 4 ,5]

 

위 코드에서 products를 통해서 quantity 항목만 남겼다. 이 후 reduce를 통해 총 수량만 뽑아 볼 것이다.

 

/* 총 수량 */
go(products,
   map(p => p.quantity),
   reduce((a, b) => a + b),
   console.log); // 15

 

이제 이 go를 함수로 만들어서 콘솔로 출력해볼 것이다.

 

const total_quantity = products => go(products,
   map(p => p.quantity),
   reduce((a, b) => a + b));
    
console.log(total_quantity(products)); // 15

 

위 함수에서 products를 받아서 그대로 go의 products 에 넣는 것을 의미하는 "products => go(products, " 부분을 pipe로 대체할 수 있다.

 

const total_quantity = pipe(
   map(p => p.quantity),
   reduce((a, b) => a + b));
    
console.log(total_quantity(products)); // 15

 

이번에는 '각 제품당 금액 × 수량'을 출력해볼 것이다.

 

const add = (a, b) => a + b; // add를 만들어 중복 제거

const total_quantity = pipe(
   map(p => p.quantity),
   reduce(add));
console.log(total_quantity(products)); // 15

const total_price = pipe(
   map(p => p.price * p.quantity),
   reduce(add));
console.log(total_price(products)); // 420000

 

위 코드를 보면 map에 전달하는 부분을 제외하고는 완전히 동일한 코드이다. 이 부분을 좀더 추상화 시켜보자.

 

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

// sum 함수를 추가해 중복되는 부분을 제거
const sum = (f, iter) => go(
  iter,
  map(f),
  reduce(add));

const total_quantity = products =>
   sum(p => p.quantity, products); 
console.log(total_quantity(products)); // 15

const total_price = products =>
   sum(p => p.price * p.quantity, products);
console.log(total_price(products)); // 420000

 

위 코드를 더 간결하게 만들 수 있는데, 앞서배운 curry를 적용해보자. sum 함수에 curry를 감싸주게 되면 아래와 같이 바꿔줄 수 있다. 

 

변경 전 변경 후
products => sum(p => p.quantity, products) products => sum(p => p.quantity)(products)

 

그리고 products를 받아서 sum의 products에 그대로 넘겨준다는 뜻은 그대로 sum(p =>p.quantity) 만 남아도 동일한 동작을 한다는 뜻이 된다.

 

변경 전 변경 후
products => sum(p => p.quantity)(products) sum(p => p.quantity)

 

curry 적용 결과는 다음과 같다.

 

const products = [
   { name: '반팔티', price: 15000, quantity: 1 },
   { name: '긴팔티', price: 20000, quantity: 2 },
   { name: '후드티', price: 40000, quantity: 3 },
   { name: '긴바지', price: 30000, quantity: 4 },
   { name: '반바지', price: 25000, quantity: 5 },
];

const add = (a, b) => a + b; // add를 만들어 중복 제거

const sum = curry((f, iter) => go(
  iter,
  map(f),
  reduce(add)));

const total_quantity = sum(p => p.quantity); 
console.log(total_quantity(products)); // 15

const total_price = sum(p => p.price * p.quantity);
console.log(total_price(products)); // 420000

 


 

장바구니 예제를 HTML로 표현하기

 

앞에서 다뤘던 예제내용을 통해 이를 HTML코드로 작성해볼 것이다. 

 

document.querySelector('#cart').innerHTML = `
   <table>
      <tr>
         <th>상품 이름</th>
         <th>가격</th>
         <th>수량</th>
         <th>총 가격</th>
      </tr>
      ${go(products,
         map(p => `
         <tr>
            <td>${p.name}</td>
            <td>${p.price}</td>
            <td><input type="number" value="${p.quantity}"></td>
            <td>${p.price * p.quantity}</td>    
         </tr>
         `)
      )}
      <tr>
         <td colspan="2">합계</td>
         <td>${total_quantity(products)}</td>
         <td>${total_price(products)}</td>
      </tr>
   </table>
`;

 

 

위에서 테이블을 출력할 때, go함수와 map함수 등등 이미 선언했던 함수들을 통해서 값을 받아서 했다. 하지만, 위 코드를 그대로 출력해보면 이상한 점이 있을 것이다.

 

 

 

상단에 ,,,, 이 같이 출력되는데, 이는 map 함수가 array를 리턴했기 때문입니다.

 

 

해결방법은 문자열 더하기를 통해 array가 아닌 하나의 값으로 만들어 줘야 한다. reduce로 array를 하나의 문자로 합쳐주면 되는데, 아래에서 reduce(add) 함수의 사용 전과 사용 후의 콘솔 출력을 비교해보자.

 

 

이제 reduce를 통해 하나의 문자열로 바꾼 후, 출력화면을 보면 ,,,, 이 사라진 것을 볼 수 있을 것이다.

 

코드를 더 줄여 보자. 앞서 sum 함수를 선언했는데, 구성을 보면 위 코드와 유사하다. 코드 구성을 sum으로 바꾸면 다음과 같이 축약할 수 있다.

 

/* 위에서 선언한 sum 함수
const sum = (f, iter) => go(
  iter,
  map(f),
  reduce(add));
*/
${go(products, sum(p => `
   <tr>
      <td>${p.name}</td>
      <td>${p.price}</td>
      <td><input type="number" value="${p.quantity}"></td>
      <td>${p.price * p.quantity}</td>    
   </tr>
`))}

 

아래를 보면, 똑같이 잘 동작함을 볼 수 있다.

 

 

위 코드들을 정리하자면 다음과 같다.

 

const products = [
   { name: '반팔티', price: 15000, quantity: 1 },
   { name: '긴팔티', price: 20000, quantity: 2 },
   { name: '후드티', price: 40000, quantity: 3 },
   { name: '긴바지', price: 30000, quantity: 4 },
   { name: '반바지', price: 25000, quantity: 5 },
];

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

const sum = curry((f, iter) => go(
   iter,
   map(f),
   reduce(add)));

const total_quantity = sum(p => p.quantity); 

const total_price = sum(p => p.price * p.quantity);

document.querySelector('#cart').innerHTML = `
   <table>
      <tr>
         <th>상품 이름</th>
         <th>가격</th>
         <th>수량</th>
         <th>총 가격</th>
      </tr>
      ${go(products, sum(p => `
      <tr>
         <td>${p.name}</td>
         <td>${p.price}</td>
         <td><input type="number" value="${p.quantity}"></td>
         <td>${p.price * p.quantity}</td>    
      </tr>
      `))}
      <tr>
         <td colspan="2">합계</td>
         <td>${total_quantity(products)}</td>
         <td>${total_price(products)}</td>
      </tr>
   </table>
`;