Front-end/JavaScript

JS FP 9편 - 함수형 프로그램

파리외 개발자 2023. 3. 15. 00:09

FP

FP 패러다임으로 프로그램이 어떻게 작성되는지에 대한 예시코드다.

UseCase

//shopping
const user = {
  name: "Kim",
  active: true,
  cart: [],
  purchase: [],
};

let amzHistory = [];

//Shopping usecase
// 1. Add items to cart
// 2. Add 3% tax to item in cart
// 3. Buy item: cart --> purchase
// 4. Empty cart
// 5. Accept refunds
// 6. Track user history
  1. 사용자 객체에는 이름, 활성여부, 장바구니, 구매내역이 있다.
  2. 상품을 장바구니에 담을 수 있다.
  3. 담긴 상품은 3%의 부가세가 부과된다.
  4. 장바구니에 담긴 상품을 구매할 수 있다.
  5. 구매 후에는 장바구니를 비워준다.
  6. 환불 기능이 있다.
  7. 사용자의 활동내역을 알 수 있다.

Compose를 사용한 function Flow

const compose =
  (f, g) =>
  (...args) =>
    f(g(...args));

purchaseItem(
  emptyCart,
  buyItem,
  applyTaxToItems,
  addItemToCart
)(user, { name: "phone", price: 200 });

function purchaseItem(...fns) {
  amzHistory.push(user);
  return fns.reduce(compose);
}

구매의 전체적인 흐름을 다루는 purchaseItem함수는

각 흐름에 해당하는 fns를 인자로 받아서 reduce를 이용해 compose를 적용한다.

compose는 두 함수를 순서대로 ...args에 적용시키는 역할을 하며

위 코드에선 emptyCart, buyItem, applyTaxToItems, addItemToCart가 

우측부터 순서대로 ...args에 해당하는 (user, {name: "phone", price: 200})에

적용된다.

ex) applyTaxToItems = f(addItemToCart = g(...args)) << first Reduce

      buyItem = f(firstReduce = g) << second Reduce

또한 사용자의 로그를 track 하기 위해 amzHistory배열에 각 상태마다의 user객체를 push 한다.

purchase Unit functions

function addItemToCart(user, item) {
  const updateCart = user.cart.concat(item);
  return Object.assign({}, user, { cart: updateCart });
}

function applyTaxToItems(user) {
  const { cart } = user;
  const taxRate = 1.3;
  const updatedCart = cart.map((item) => {
    return { name: item.name, price: item.price * taxRate };
  });
  return Object.assign({}, user, { cart: updatedCart });
}

function buyItem(user) {
  return Object.assign({}, user, { purchase: user.cart });
}

function emptyCart(user) {
  return Object.assign({}, user, { cart: [] });
}

위에서부터 차례로 

  • 장바구니에 상품 담기
  • 부가세 3% 과세
  • 장바구니 상품 구매
  • 장바구니 비우기

를 담당하는 함수들이다.

모든 함수들은 side Effect를 방지하기 위해 Object.assign을 사용해서

새로운 객체를 리턴해준다.

Result

purchaseItem함수에 user객체와 구매할 상품 객체를 인자로 넣어주니

기존 user객체에 없었던 purchase속성에

상품 객체가 담긴 것을 확인할 수 있다.

history

purchase 할 때마다 push 되는 user객체에 따라 사용자 구매 로그를 작성할 수 있다.

자연스럽게 해당 기록을 사용해 refund 요구사항을 충족시킬 수 있다.

add Flow

//if i want to add another func
function refundItem() {
  //just go back history of user
}

여기서 코드를 작성하지 않겠지만 만약 환불을 하기 위해서라면

해당 함수는 history에 있는 사용자 객체를 현재 사용자 객체에 대입시키는

함수가 될 것이다.

또는, purchase flow에 새 기능을 추가하고 싶다면 unit function을 작성한 후

purchase의 fns인자에 추가해주기만 한다면 

구매 흐름에 해당 unit함수 동작이 추가될 것이다.

전체 코드

//shopping
const user = {
  name: "Kim",
  active: true,
  cart: [],
  purchase: [],
};

let amzHistory = [];

//Shopping usecase
// 1. Add items to cart
// 2. Add 3% tax to item in cart
// 3. Buy item: cart --> purchase
// 4. Empty cart
// 5. Accept refunds
// 6. Track user history

const compose =
  (f, g) =>
  (...args) =>
    f(g(...args));

purchaseItem(
  emptyCart,
  buyItem,
  applyTaxToItems,
  addItemToCart
)(user, { name: "phone", price: 200 });

function purchaseItem(...fns) {
  amzHistory.push(user);
  return fns.reduce(compose);
}

function addItemToCart(user, item) {
  const updateCart = user.cart.concat(item);
  return Object.assign({}, user, { cart: updateCart });
}

function applyTaxToItems(user) {
  const { cart } = user;
  const taxRate = 1.3;
  const updatedCart = cart.map((item) => {
    return { name: item.name, price: item.price * taxRate };
  });
  return Object.assign({}, user, { cart: updatedCart });
}

function buyItem(user) {
  return Object.assign({}, user, { purchase: user.cart });
}

function emptyCart(user) {
  return Object.assign({}, user, { cart: [] });
}

 

'Front-end > JavaScript' 카테고리의 다른 글

JS 비동기 1편 - Promise  (0) 2023.03.18
FP vs OOP  (0) 2023.03.16
JS FP 8편 - 함수 합성(Compose, Pipe, Arity)  (0) 2023.03.13
JS FP 7편 - Memoization  (0) 2023.03.12
JS FP 6편 - 부분 적용 함수(Partial Application)  (0) 2023.03.11