개요
객체지향 프로그래밍은 필수적인 패러다임으로서 다루는 책과 글은 많다.
반면, 함수형 프로그래밍은 필수적이지도 않으며,
어떤 부분이 좋다고 콕 집어 말하기 어렵다.
이는 함수형 프로그래밍이 범용 패러다임이기 때문이다.
결국, 어디에서나 잘 어울릴 수 있다는 뜻이기도 하다
이 글은 아래 책을 읽고 난 후 작성되었습니다.
- 쏙쏙 들어오는 함수형 코딩 / 에릭 노먼드
목표
함수형 프로그래밍의 주요 개념과 관점을 이해하고, 함수지향 설계와 아키텍쳐를 학습한다.
여정
일급(First Class)
우리는 지금까지 코드를 분류하고, 쪼개면서 함수형 프로그래밍을 배웠다.
이번에는 함수에 함수를 더하는 고차 함수를 만들어보도록 하자.
아래와 같은 간단한 함수가 있다.
객체에 key-value 쌍을 넣어서 돌려주는 objectSet 함수를 이용해서
장바구니 내에 값을 변경하는 코드이다.
function objectSet(obj, key, value) {
let copy = obj.assign({}, obj); // 객체 복사
obj[key] = value;
return copy;
}
function setPriceByName(cart, name, price) {
let item = cart[name];
let newItem = objectSet(item, 'price', price);
return objectSet(cart, name, newItem);
}
계산 함수를 만들어서 사용하는 것까지 충분히 좋아보인다.
function setQuantityByName(cart, name, quantity) {
let item = cart[name];
let newItem = objectSet(item, 'quantity', quantity);
return objectSet(cart, name, newItem);
}
하지만 이런 함수가 속성별로 수십가지가 된다면 문제가 있다.
함수 이름에도 암묵적인 인자가 있는 것이다.
따라서, 아래와 같이 함수를 바꿀 수 있다.
function setFieldByName(cart, name, fieldName, value) {
let item = cart[name];
let newItem = objectSet(item, fieldName, value);
return objectSet(cart, name, newItem);
}
function setPriceByName(cart, name, price) {
return setFieldByName(cart, name, 'price', price);
}
function setQuantityByName(cart, name, quantity) {
return setFieldByName(cart, name, 'quantity', quantity);
}
이렇게 함수 이름을 직접 수정할 순 없지만,
함수명 일부를 인자로 바꿔 문제를 해결했다.
이는 함수명 일부를 일급 값으로 바꿔 해결했다는 말과 같다.
일급(first-class)라는 뜻은 아래 행동을 할 수 있다는 뜻이다.
- 변수에 할당하거나
- 함수의 인자로 넘기거나
- 함수의 리턴값이 되거나
- 배열이나 객체에 담거나
그리고 가장 중요한 사실은 함수도 일급이 될 수 있다.
고차 함수(Higher-Order Function)
- 함수도 변수에 할당할 수 있다.
- 함수도 함수의 인자로 넘길 수 있다.
- 함수도 함수의 리턴값이 될 수 있다.
- 함수도 배열이나 객체에 담길 수 있다.
이렇게 이 중에서 우리가 알아볼 것은
함수를 이용하는 인자로 받는 함수, 고차 함수(higher-order function)이다.
예를 들어 아래와 같은 간단한 코드가 있다.
foods = ['pizza', 'burger', 'donuts'];
for (let i = 0; i < foods.length; i++) {
console.log(foods[i]);
cook(foods[i]);
eat(foods[i]);
}
우리가 배웠던대로 코드를
액션이 아닌 계산으로 만들면 아래와 같다.
function cookAndEatFoods(foods) {
for (let i = 0; i < foods.length; i++) {
console.log(foods[i]);
cook(foods[i]);
eat(foods[i]);
}
}
여기서 반복문 안의 내용만 따로 함수로 만들면 이렇게 된다.
function cookAndEat(foods) {
console.log(foods[i]);
cook(foods[i]);
eat(foods[i]);
}
function repeatCookAndEat(foods) {
for (let i = 0; i < foods.length; i++) {
cookAndEat(foods[i]);
}
}
그럼 한번 더 함수를 인자로 받는 계산으로 만들면?
function repeatAction(array, action) {
for (let i = 0; i < array.length; i++) {
action(array[i]);
}
}
repeatAction(foods, cookAndEat);
이렇게 사용할 수 있다.
이것이 바로 함수의 인자로 함수를 사용한 고차함수 형태이다.
우리가 주로 겪는 지저분한 코드 중에 try/catch도
고차함수를 이용하면 좀 더 깔끔하게 감쌀 수 있다.
function withLogging(func) {
try {
func();
} catch (e) {
console.log(e);
}
}
withLogging(() => { saveUserData(user); }
이렇게 하면 try/catch가 필요한 모든 코드에 특정한 행동을 심을 수도 있다.
function callOnCondition(func, condition) {
return () => {
if (condition()) {
func();
}
}
}
function callAfterDelay(func, delay) {
return () => {
setTimeout(func, delay);
}
}
이러한 방식을 응용하면 콜백으로 바꾸기 방식의 리팩터링을 할 수 있다.
Wrapping
함수를 굳이 한번 더 감싸는 이유는
매개변수를 전달하기 전 실행되면 안되기 때문이다.
콜백으로 바꾸기
기존에 작성한 것과 비슷한 아래 코드를 보자.
모두 동일한 과정을 통해 동작한다.(배열 복사 → 행동 → 복사한 객체 반환)
function arraySet(array, key, value) {
let copy = array.slice(); // 배열 복사
copy[key] = value; // 행동
return copy; // 복사한 객체 반환
}
function push(array, value) {
let copy = array.slice(); // 배열 복사
copy.push(value); // 행동
return copy; // 복사한 객체 반환
}
따라서, 아래와 같이 고칠 수 있다.
function withArrayCopy(array, action) {
let copy = array.slice(); // 배열 복사
action(copy); // 행동
return copy; // 복사한 객체 반환
}
function arraySet(array, key, value) {
return withArrayCopy(array, copy => copy[key] = value);
}
function push(array, value) {
return withArrayCopy(array, copy => copy.push(value));
}
이렇게 하면, 특정 행동만 교체 가능하므로 쉽게 재사용할 수 있게 된다.
마무리
이번 시간에는 전달할 수 있는 대상인 일급의 개념과
일급 함수를 사용하는 고차함수에 대해 학습했다.
다음 시간에는 체이닝(Chaining)을 통해 함수를 조합해보자.
'Do you know?' 카테고리의 다른 글
아시나요? 마이크로 서비스와 도메인 주도 설계 (1) (0) | 2023.08.13 |
---|---|
아시나요? 함수형 프로그래밍 - (5) 체이닝(Chaining) (0) | 2023.08.06 |
아시나요? 함수형 프로그래밍 - (3) 추상화 레벨 (0) | 2023.07.23 |
아시나요? 함수형 프로그래밍 - (2) 분류하기 (0) | 2023.07.18 |
아시나요? 함수형 프로그래밍 - (1) 개념 (0) | 2023.07.16 |