함수형 프로그래밍 #03 | Array: 여러 개일 수 있는 값
2022년 09월 05일 15:38
📌 비결정적인 함수
배열이나 리스트는 0개 이상의 데이터를 가지기 때문에 이를 return할 때 0개 이상의 값이 반환이 되는데 이 개수가 결정되지 않기 때문에 비결정적이라고도 부른다. 의미상 값이 여러개가 되기도 하지만 배열, 리스트에 의해 여러 값은 하나의 값으로 취급과 같기 때문에 순수함수가 아니라고 볼 수는 없다.
📌 고차함수
위의 판에박힌 코드들을 해결하기 위해 함수형 프로그래밍에서는 고차함수를 사용한다. 고차함수는 하나 이상의 함수를 인수로 취하는 함수 || 함수를 결과로 반환하는 함수를 말한다.
대표적인 고차함수인 map을 제네릭으로 구현해보자.
export const map = <A, B>(arr: Array<A>, f: (a: A) => B): Array<B> => {
const result: Array<B> = [];
for (const value of arr) {
result.push(f(value));
}
return result;
}map함수 내에서 for루프를 사용하기는 했지만 사용하는 입장에서는 map함수가 어떻게 구현되어 있는지 신경쓸 필요가 없고, 주어진 배열의 모든 원소에 주어진 함수를 사용해서 새로운 배열을 얻을 수 있다는 것만 알고있으면 된다. 즉, 기존에 직접 다루던 부수효과를 분리해서 격리한 후에 별도의 효과로 취급할 수 있도록 추상화 한 것이다.
📌 장바구니 프로그램 (명령형)
//cart.ts
export interface Item {
code: string;
outOfStock: boolean;
name: string;
price: number;
quantity: number;
}import {cart} from "./cart.js";
const list = () => {
let html = "<ul>";
let totalCount = 0;
let totalPrice = 0;
for (let i = 0; i < cart.length; i++) {
html += "<li>";
html += `<h2>${cart[i].name}</h2>`;
html += `<div>가격: ${cart[i].price}</div>`;
html += `<div>수량: ${cart[i].quantity}</div>`;
html += "</li>";
totalCount += cart[i].quantity;
totalPrice += cart[i].price * cart[i].quantity;
}
html += "</ul>";
html += `<h2>전체 수량: ${totalCount}`;
html += `<h2>전체 가격: ${totalPrice}`;
return html;
}
const app = document.getElementById("app");
if (app !== null) {
app.innerHTML = `
<h1>장바구니</h1>
${list()}
`;
}위에서 전체수량, 전체가격 계산하는 코드를 분리하기 위해 for문을 하나 더 작성해볼 수도 있다. 만약 cart[i].outOfStock이 true일 때 취소선을 긋는 기능이 추가된다고 했을 때 for문 안에 if else문을 추가해서 코드를 작성할 것이다. 즉, 명령형으로 작성한 이 코드는 요구사항이 늘어날 수록 대응해야하는 코드가 점점 많아지는 것이다. 게다가 기존 요구사항이 변경되면 그것까지 반영을 해야하고, 이렇게 되면 변경사항이 늘어나면 실수하기가 쉬워진다.
📌 장바구니 프로그램 (순수함수)
장바구니를 그린다.
-> 장바구니를 순회하면서
-> 화면에 상품 이름, 가격, 수량을 표시한다.
전체 가격과 전체 수량도 화면에 그린다.
-> 2번의 동작을 수행할 때 totalPrice, totalCount에 값을 누적한다.
재고 없는 상품의 처리
-> 2번과 6번의 동작을 수행할 때 재고 여부에 따라 다르게 동작시킨다.
아이템 목록 표시
- 재고가 있는 아이템
- 재고가 없는 아이템
전체 수량 표시
전체 가격 표시
const stockItem = (item: Item): string => `
<li>
<h2>${item.name}</h2>
<div>가격: ${item.price}</div>
<div>수량: ${item.quantity}</div>
</li>
`;
const outOfStockItem = (item: Item): string => `
<li class="gray">
<h2>${item.name} (품절)</h2>
<div class="strike">가격: ${item.price}</div>
<div class="strike">수량: ${item.quantity}</div>
</li>
`;
const item = (item: Item): string => {
if (item.outOfStock) {
return outOfStockItem(item);
} else {
return stockItem(item);
}
}
const totalCalculator = (list: Array<Item>, getValue: (item: Item) => number) => {
let total = 0;
for (let i = 0; i < list.length; i++) {
if (list[i].outOfStock === false) {
total += getValue(list[i]);
}
}
return total;
}
const totalCount = (list: Array<Item>): string => {
const totalCount = totalCalculator(list, (item) => item.quantity);
return `<h2>전체 수량: ${totalCount}상자</h2>`;
}
const totalPrice = (list: Array<Item>): string => {
const totalPrice = totalCalculator(list, (item) => item.price * item.quantity);
return `<h2>전체 가격: ${totalPrice}원</h2>`;
}
const list = (list: Array<Item>) => {
let html = "<ul>";
for (let i = 0; i < list.length; i++) {
html += item(list[i]);
}
html += "</ul>";
return `${html}`;
}
const app = document.getElementById("app");
if (app !== null) {
app.innerHTML = `
<h1>장바구니</h1>
${list(cart)}
${totalCount(cart)}
${totalPrice(cart)}
`;
}📌 장바구니 프로그램 (순수함수 + 메소드 체이닝)
totalCalculator를 메소드 체이닝으로 해보자.
전체 목록중 재고가 있는 상품만 getValue를 실행하고 그 값을 모두 더한다.
- 재고가 있는 상품만 분류하기
- 분류된 상품들에 대해서 getValue 실행하기
- getValue가 실행된 값 모두 더하기
const totalCalculator = (list: Array<Item>, getValue: (item: Item) => number) => {
return list
.filter(item => item.outOfStock === false)
.map(getValue)
.reduce((total, value) => total + value, 0);
}list를 메소드 체이닝으로 해보자.
- 목록에 있는 아이템을 태그로 변경
- 태그의 목록을 모두 하나의 문자열로 연결
const list = (list: Array<Item>) => {
return `
<ul>
${list
.map(item)
.reduce((tags, tag) => tags + tag, "")}
</ul>
`;
}