JS 클로저 - 커링 함수

돼지피그·2023년 5월 27일
0

커링함수란(currying function)

여러 개의 인자를 받는 함수를 하나의 인자만 받는 함수로 나눠서 순차적으로 호출 될 수 있게 체인 형태로 구성한 것을 말합니다.

커링은 한 번에 하나의 인자만 전달하는 것을 원칙으로 합니다.

또한 중간 과정상의 함수를 실행한 결과는 그 다음 인자를 받기 위해 대기만 할 뿐, 마지막 인자가 전달되기 전까지는 원본 함수가 실행되지 않습니다.

(부분 적용 함수는 여러 개의 인자를 전달할 수 있고, 실행 결과를 재실행할 때 원본 함수가 무조건 실행됩니다.)

//커링함수 예시 (1)
var curry3 = function(func) {
  return function(a) {
    return function(b) {
      return func(a, b);
    };
  };
};

var getMaxWith10 = curry3(Math.max)(10);
console.log(getMaxWith10(8)); // 10
console.log(getMaxWith10(25)); // 25

var getMinWith10 = curry3(Math.min)(10);
console.log(getMinWith10(8)); // 8
console.log(getMinWith10(25)); // 10

필요한 인자 개수만큼 함수를 만들어 계속 리턴해주다가 마지막에 한번에 조합해서 리턴해줍니다.

//커링함수 예시 (2)
var curry5 = function(func) {
  return function(a) {
    return function(b) {
      return function(c) {
        return function(d) {
          return function(e) {
            return func(a, b, c, d, e);
          };
        };
      };
    };
  };
};
var getMax = curry5(Math.max);
console.log(getMax(1)(2)(3)(4)(5));

하지만 인자가 많아질 수록 가독성이 떨어지는 단점이 있습니다.
그래서 ES6에서는 화살표함수를 사용해 한 줄로 표기할 수 있습니다.

var curry5 = fuc => a=> b=> c=> d=> e=> func(a,b,c,d,e);

위와 같이 화살표 함수로 구현하면 화살표 순서에 따라 함수에 값을 차례로 넘겨주다 마지막에 func이 호출될 거라는 흐름이 한눈에 파악이 됩니다.
각 단계에서 받은 인자들을 모두 마지막 단계에서 참조할 것이므로 GC되지 않고 메모리에 차곡차곡 쌓였다가, 마지막 호출로 실행 컨텍스트가 종료된 후에야 한꺼번에 GC의 수거 대상이 됩니다.

이런 커링 함수가 유용한 경우가 있습니다.
당장 필요한 정보만 받아서 전달하고 또 필요한 정보가 들어오면 전달하는 식으로 하면 결국 마지막 인자가 넘어갈 때까지 함수 실행을 미루는 셈이 됩니다. 함수형 프로그래밍에서는 지연실행(lazy execution)이라고 칭합니다.
따라서 원하는 시점까지 지연시켰다가 실행하는 것이 요긴한 상황이라면 커링을 쓰기에 적합할 것입니다.
혹은 자주 쓰이는 함수의 매개변수가 항상 비슷하고 일부만 바뀌는 경우에도 적절한 후보가 될 것입니다.

var getInformation baseUrl path id=fetch(baseUrl+ path + '/' + id);

보통 fetch로 HTTP요청을 할 때 baseURL은 몇 개로 고정되지만 나머지 path나 id값은 매우 많을 수 있습니다. 이런 상황에서 서버에 요청할 때 매번 baseURL부터 전부 기입해주기보다는 공통적인 요소는 먼저 기억시켜두고 특정 값(id)만으로 서버에 요청을 수행하는 함수를 만들어두는 편이 개발 효율성이나 가독성 측면에서 더 좋을 것입니다.


var imageUrl = "http://imageAddress.com/";
var productUrl = "http://productAddress.com/";

// 이미지 타입별 요청 함수 준비
var getImage = getInformation(imageUrl); // http://imageAddress.com/
var getEmoticon = getImage("emoticon"); // http://imageAddress.com/emociton
var getIcon = getImage("icon"); // http://imageAddress.com/icon
// 제품 타입별 요청 함수 준비

var getProduct = getInformation(productUrl); // http://productAddress.com/
var getFruit = getProduct("fruit");
// http://productAddress.com/fruit
var getVegetable = getProduct("vegetable"); // http://productAddress.com/vegetable
// 실제 요청
var emoticon1 = getEmoticon(100); // http://imageAddress.com/emoticon/100
var emoticon2 = getEmoticon(102);
// http://imageAddress.com/emoticon/102

var icon1 = getIcon(205); // http://imageAddress.com/icon/205
var icon2 = getIcon(234); // http://imageAddress.com/icon/234
var fruit1 = getfruit(300); // http://productAddress.com/fruit/300
var fruit2 = getFruit(400); // http://productAddress.com/fruit/400
var vegetable1 = getVegetable(456); // http://productAddress.com/vegetable/456
var vegetable2 = getVegetable(789); // http://productAddress.com/vegetable/789

이런 식으로 fetch 함수를 커링으로 사용할 수 있습니다.

Redux의 미들웨어로 예시를 보자면,

// Redux Middleware "logger"
const logger = (store) => (next) => (action) => {
  console.log("dispatching", action);
  console.log("next state", store.getState());
  return next(action);
};

//Redux Middleware "thunk"
const thunk = (store) => (next) => (action) => {
  return typeof action === "function"
    ? action(store.dispatch, store.getState)
    : next(action);
};

위 코드에서 두 미들웨어는 공통적으로 store, next, action 순으로 인자를 받는데,

store는 프로젝트 내에서 한 번 생성된 이후로는 바뀌지 않는 속성이고 action의 경우는 매번 달라집니다.

그러니까 store와 next값이 결정되면 Redux 내부에서 logger 또는 thunk에 store, next를 미리 넘겨서 반환된 함수를 저장시켜놓고,

이후에는 action만 받아서 처리할 수 있게 한 것입니다.

0개의 댓글