Chapter 11

일급 함수 II

  • 11.1 카피-온-라이트 리팩터링
  • 11.2 함수를 리턴하는 함수
  • 11.3 카피-온-라이트에 적용하기
  • 11.4 방어적 복사와 조합

기존 함수의 동작을 변경하지 않고, 새로운 동작을 추가한 함수를 만드는 것 — 이게 "함수를 리턴하는 함수" 패턴의 핵심이야.

10장에서 일급 함수와 고차 함수의 기본을 배웠으니, 11장에서는 이걸 실전에 적용해. 6장에서 만들었던 카피-온-라이트 함수들을 떠올려 봐 — setPriceByName, setQuantityByName 같은 거. 거의 같은 코드인데 setPricesetQuantity만 달랐잖아. 10장에서 배운 "본문을 콜백으로 바꾸기"를 적용하면:

function modifyItemByName(cart, name, modify) {
  var cartCopy = cart.slice();
  for (var i = 0; i < cartCopy.length; i++) {
    if (cartCopy[i].name === name)
      cartCopy[i] = modify(cartCopy[i]);
  }
  return cartCopy;
}

// 사용
var cart = modifyItemByName(cart, "shoes", function(item) {
  return setPrice(item, 13);
});

카피-온-라이트 패턴도 더 일반화할 수 있어. 배열이나 객체를 복사하고 수정하는 패턴 자체를 추상화하는 거지:

function withArrayCopy(array, modify) {
  var copy = array.slice();
  modify(copy);  // 복사본을 변경 (원본은 안전)
  return copy;
}

// 사용
var sortedArray = withArrayCopy(array, function(copy) {
  copy.sort();  // 파괴적 연산을 안전하게 사용
});

Array.sort() 같은 파괴적(mutating) 메서드도 withArrayCopy 안에서 쓰면 카피-온-라이트가 돼. 복사본만 변경하니까.

10장에서는 함수를 인자로 받는 고차 함수를 봤는데, 이번에는 함수를 리턴하는 고차 함수야. 7장에서 방어적 복사 코드를 떠올려 봐 — 방어적 복사를 할 때마다 deepCopy를 앞뒤로 감싸야 했잖아. 이 패턴이 반복되면 실수하기 쉬운데, 함수를 리턴하는 함수로 이 패턴을 감싸면:

function wrapWithDeepCopy(f) {
  return function(arg) {
    var copy = deepCopy(arg);
    var result = f(copy);
    return deepCopy(result);
  };
}

var safeAddItem = wrapWithDeepCopy(add_item);
// safeAddItem을 호출하면 자동으로 deepCopy가 적용됨

wrapWithDeepCopy는 함수 f를 받아서, f를 방어적 복사로 감싼 새로운 함수를 리턴해.

이런 식으로 자바스크립트의 파괴적 배열 메서드들을 한번에 안전하게 만들 수도 있어:

var push = wrapWithCopyOnWrite(Array.prototype.push);
var pop = wrapWithCopyOnWrite(Array.prototype.pop);
var sort = wrapWithCopyOnWrite(Array.prototype.sort);

실제로 이렇게 범용적으로 만들려면 엣지 케이스가 많지만, 아이디어 자체가 중요해 — 반복되는 패턴을 고차 함수로 자동화할 수 있다는 것.

로깅 추가도 같은 패턴이야:

function wrapWithLogging(f) {
  return function() {
    var result;
    try {
      result = f.apply(this, arguments);
    } catch (e) {
      logToServer(e);
      throw e;
    }
    return result;
  };
}

var safeCalcTotal = wrapWithLogging(calc_total);

"함수를 리턴하는 함수" 패턴은 다른 이름으로 함수 래퍼(wrapper) 또는 **데코레이터(decorator)**라고도 하거든. 원래 함수를 감싸서 부가 기능을 입히는 거야. 이 패턴을 체이닝할 수도 있어 — var safeFn = wrapWithLogging(wrapWithDeepCopy(originalFn)); 이렇게 하면 originalFn에 방어적 복사 + 로깅이 한번에 적용돼.


정리

11장 읽고 기억할 거 세 가지:

  1. 카피-온-라이트의 반복 패턴도 고차 함수로 추상화 가능. withArrayCopy 같은 래퍼로 파괴적 메서드를 안전하게 감싼다
  2. 함수를 리턴하는 함수 = 기존 함수에 동작을 추가한 새 함수를 만든다. wrapWithDeepCopy, wrapWithLogging 같은 패턴
  3. 래퍼 함수는 체이닝 가능. 여러 래퍼를 겹쳐서 다양한 부가 기능을 조합할 수 있다