개요
객체지향 프로그래밍은 필수적인 패러다임으로서 다루는 책과 글은 많다.
반면, 함수형 프로그래밍은 필수적이지도 않으며,
어떤 부분이 좋다고 콕 집어 말하기 어렵다.
이는 함수형 프로그래밍이 범용 패러다임이기 때문이다.
결국, 어디에서나 잘 어울릴 수 있다는 뜻이기도 하다
이 글은 아래 책을 읽고 난 후 작성되었습니다.
- 쏙쏙 들어오는 함수형 코딩 / 에릭 노먼드
목표
함수형 프로그래밍의 주요 개념과 관점을 이해하고, 함수지향 설계와 아키텍쳐를 학습한다.
여정
코드 분류하기
지난 포스팅의 마지막에 등장한 코드를 다시 가져와액션과 계산, 데이터로 분류해보았다.
// 전역변수는 액션이다 = 변경 가능하기 때문
let shoppingCart = [];
let shoppingCartTotal = 0;
function addToCart(name, price) {
// 전역변수를 변경하는 것도 액션이다.
shoppingCart.push({
name: name,
price: price
});
calculateTotal();
}
function calculateTotal() {
// 전역변수를 변경하는 것도 액션이다.
shoppingCartTotal = 0;
for (let i = 0; i < shoppingCart.length; i++) {
shoppingCartTotal += shoppingCart[i].price;
}
setCartTotalDom(); // DOM을 조작하는 것은 액션이다.
}
function setCartTotalDom() {
// DOM manipulation
}
모든 코드가 액션으로 전염되었다.
전염시킨 요인은 전역변수이다.
왜 전역변수를 사용하는게 액션으로 취급받는 것일까?
입력과 출력 찾아내기
모든 함수에는 입력과 출력이 있다.매개변수로 들어오는 명시적 입력과, return으로 반환되는 명시적 출력이 대표적이다.
하지만 아래와 같이 전역변수로 이루어진 암시적 입력,출력도 있다.
function addTototal(amount) { // 명시적 입력
console.log('Old total: ' + shoppingCartTotal); // 암시적 입력
shoppingCartTotal += amount; // 암시적 출력
return shoppingCartTotal; // 명시적 출력
}
암시적 입력과 출력이 있으면 액션이 된다.
때문에 전역변수를 사용하는 것이 액션으로 취급받게 된다.
(이를 '부수효과'라고 한다)
이제 이 암시적 입력과 출력을 제거해보자.
액션을 계산으로 분리
시원하게 전체 고친 코드를 보면 아래와 같다.
let shoppingCart = [];
let shoppingCartTotal = 0;
// 전역변수(cart)를 읽는 것은 액션이다
function addToCart(cart, name, price) {
let cartItem = makeCartItem(name, price);
cart = addCartItem(cart, cartItem);
return calculateCartTotal(cart);
}
// 암시적 입출력이 없으므로 계산이다.
function makeCartItem(name, price) {
return {
name: name,
price: price
};
}
// 암시적 입출력이 없으므로 계산이다.
function addCartItem(cart, cartItem) {
// 장바구니 케이스에 맞게 재사용
return addElementLast(cart, cartItem);
}
// 암시적 입출력이 없으므로 계산이다.
function addItemToArray(array, item) {
// 일반적인 상황에서도 사용할 수 있도록 작성
let newArray = array.slice(); // ★ 배열 복사본으로 계산 ★
newArray.push(item);
return newArray;
}
// DOM을 조작하므로 액션이다.
function calculateCartTotal(cart) {
let totalPrice = calculateTotal(cart);
setCartTotalDom(totalPrice);
return totalPrice;
}
// 암시적 입출력이 없으므로 계산이다.
function calculateTotal(array) {
let total = 0;
for (let i = 0; i < array.length; i++) {
total += array[i].price;
}
return total;
}
// DOM을 조작하므로 액션이다.
function setCartTotalDom(totalPrice) {
// DOM manipulation
}
어떤가? 전역변수를 참조하더라도 계산으로 분리해낼 수가 있다.
액션과 계산을 분리하면 모든 코드에 액션이 전염되는 것을 막을 수 있다.
여기서 중요한 원칙이 등장한다.
바로 불변성의 원칙이다.
function addItemToArray(array, item) {
// 일반적인 상황에서도 사용할 수 있도록 작성
let newArray = array.slice(); // ★ 배열 복사본으로 계산 ★
newArray.push(item);
return newArray;
}
복사본 만들기 → 복사본 변경하기 → 복사본 리턴하기의 절차를 지닌 코드이다.
이 코드는 놀랍게도 쓰기가 아니라 읽기이다.
기존 배열을 변경하지 않고, 정보를 리턴했기 때문이다.
그렇다. 우리는 쓰기를 읽기로 바꿀 수 있다.
이를 바탕으로 장바구니에서 아이템을 제거하는 함수를 작성하면 아래와 같다.
// 전역변수(cart)를 읽어오므로 액션
function removeFromCart(cart, name) {
let idx = null;
for (let i = 0; i < cart.length; i++) {
if (cart[i].name === name) {
idx = i;
break;
}
}
if (idx === null) { return cart; }
return removeCartItem(cart, idx);
}
// 장바구니용 계산으로 작성
function removeCartItem(cart, idx) {
return removeElement(cart, idx, 1);
}
// 일반적인 상황에서도 사용가능한 불변 계산
function removeElement(array, idx, count) {
let newArray = array.slice(); // 배열 복사본으로 계산
newArray.splice(idx, count); // idx 위치에서 count개 만큼 삭제
return newArray;
}
이렇게하면 쓰기를 읽기로 변경하면
삭제함수 역시 액션을 계산으로 분리해낼 수 있다.
Q & A
- 장바구니가 바뀔때마다 배열을 복사하면 느려지지 않나요?
책의 저자(에릭 노먼드)는 애플리케이션 개발에 있어서 미리 최적화하지 않기를 권장했다.
또한, 중첩 데이터 구조에서는 최상위 데이터 구조만 복사되고,
안쪽 데이터는 같은 데이터를 참조하고 있기 때문에 생각보다 많은 복사가 일어나지 않는다고 말했다.
덧붙여 가비지 콜렉터는 매우 빠르고,
불변 데이터 구조를 지키면서 대용량 고성능 시스템을 구현한 사례는 많이 있다고 강조했다.
- 읽기와 쓰기를 동시에 하는 코드는 어떻게 불변성을 지키나요?
읽기와 쓰기 함수로 분리해서 작성하면 해결할 수 있습니다.
// 읽기와 쓰기함수로 분리
function lastElement(array) { // 읽기
return array[array.length - 1];
}
function dropLast(array) { // 쓰기
let newArray = array.slice();
newArray.pop();
return newArray;
}
마무리
지금까지 우리가 작성한 코드는 크게 문제가 없어보인다.하지만 다른 관점에서 본다면, 냄새나는 코드라고 할 수 있을 정도이다.
다음 시간에는 함수형 프로그래밍의 계층화 설계에 대해서 알아볼 것이다.
'Do you know?' 카테고리의 다른 글
아시나요? 함수형 프로그래밍 - (4) 고차 함수 (0) | 2023.07.25 |
---|---|
아시나요? 함수형 프로그래밍 - (3) 추상화 레벨 (0) | 2023.07.23 |
아시나요? 함수형 프로그래밍 - (1) 개념 (0) | 2023.07.16 |
아시나요? - 객체지향의 사실과 오해 (0) | 2023.05.14 |
아시나요? 웹서버와 WAS, 그리고 Nginx (0) | 2023.03.09 |