[JS] Currying function

colki·2021년 4월 29일
0
post-custom-banner

Currying function

여러개의 인자를 받는 함수를 쪼개서
인자를 하나씩 받고, 다 받았을 때 콜백함수를 실행하는 함수이다.

function foo (add, a, b, c) {
	return add(a, b, c);
}

foo(
  function add (a, b, c){
    return a + b + c;
  }, 
  1, 2, 3
); // 6
  
위와 같은 함수를 커링기법을 이용해서 쪼갤 수 있다는 것.

function curry (func) {
  return function(a) {
    return function(b) {
      return function(c) {
        return func(a, b, c);
      }
    }
  }
}

리턴의 리턴을 거듭하는 위와 같은 형태이다.
클로저로 인해 가장 내부에서도 a, b, c를 기억해서 받아 온다.
이 함수를 실행해보자

functon add(a, b, c){ return a + b + c; }

foo = curry(add); // add함수를 먼저 받는다.
foo1 = foo(1);
foo2 = foo1(2);
foo3 = foo2(3)

a, b, c 인자의 개수 3개 만큼 foo함수 역시 3번 생성되었다.

여기서 인자개수 = 생성된 함수개수 같을때 결과가 발생하는 것을 알 수 있다.

⏸ 다음과 같이 표현할 수도 있다.

curry(foo)(1)(2)(3);
//or
const curry = add => a => b => c => add(a, b, c);

bind

We can reuse a piece of code give it a partial parameter and create functions that are extensible.

this를 바인딩할 때 주로 사용했던 bind도 이와 유사한 기능을 한다.
인자를 미리 일부만 넘겨주고 필요할때 추가 인자를 주면서 실행할 수 있다.

function multiply(a, b) {
  return a * b;
}

const multiplyByTwo = multiply.bind(this, 2);

console.log(multiplyByTwo(4)); // 8

그런데 인자가 몇 개가 될지 모르니 일일히 작성하는데는 한계가 있다.
현업에서는 lodash등의 라이브러리를 사용하겠지만,
어떻게 실행되는 지 lodash를 구현하면서
커리를 먹어🍛..아니.. 이해하려고 한다! 😁

💡 _curry

function curry(func) {
// 가장 먼저 받는 func 콜백함수가 curry의 매개변수가 된다.
// 커리는 함수를 리턴해야 하는 함수이니, 우리는 함수를 뱉어야 한다.

  return function (arg) {
  // 위 curry의 모양을 생각해보자. 
  // 여기에는 a인자 하나만 들어오게 된다.
  // 그런데 추후 계속 함수는 리턴 즉 생성될거고 
  // 그때마다 인자가 있다면 생기는 함수의 인자로 또 들어올 것이다.
  // 그렇다면 우리는 인자를 기억했다가 마지막에 콜백에게 건네야 한다.
  // 이 첫번째 함수부터 차곡차곡 기억하게 하려면
  // 여기 밖에 변수를 만들어야 한다. 
  // 그렇다면 다시 세팅해보자.
  .
  .
  .

함수의 개수가 인자의 개수 이상**일 때
최종적으로 함수가 실행되어 결과를 뱉는다 했었다.


function _curry(func) {
	
  const funcLength = func.length;
  // 여기에서 함수의 개수를 미리 확인할 수 있다. 
  // 함수.length = 함수의 인자 개수 이기 때문!!!!
  
  
  let args = []; 
  // 들어올 인자들을 배열에 넣을 것이다.
  // const를 안쓰는 이유는 재할당할 에정이라 그렇다.
  
  return function executeFunc(...arg) {
  // 재호출을 위해 익명이 아닌 기명함수로 작성해준다.
  //...arg로 세팅해주면 arg를 바로 배열로 사용할 수 있다.
  // 음 먼저 인자의 개수를 판별할 수 있는 조건문이 있으면 좋겠다.
  // 만약 인자가 없다면, 즉 arg가 빈배열이라면 
  // 함수 혼자 실행해야 한다.
  // 인자에 따라서 아래 구문이 없어도 작동할 수도 있다.
  
    if (!arg) {
      func();
      return;
      //or return executeFunc;
    }
    
    // 그럼 여기 아래는 arg.length가 있을 때다.
    // length가 존재하려면 args에 인자가 들어가야 한다.
    
    args = args.concat(arg);
    // concat을 이용해서 인자가 있을경우 추가적으로 생성될 함수의
    // 인자를 받아서 args배열에 지속적으로 합쳐질 수 있게 
    // 위와 같은 코드를 심어준다.
    // 허나 여기까지만 작성하고 돌리면 인자가 하나만 담기고 
    // 그 뒤로는 에러가 뜬다.
    // 왜냐면 아직 함수를 호출해서 생성하는 구문이 없기 때문.
    
    // 처음에 언급했던 내용중에
    // 인자의 개수와 함수의 개수가 일치할 때 결과가 나온다고 했다.
    // 그렇다면 인자의 개수가 함수보다 적다면 추가적으로 
    // 함수를 생성해야 한다. 즉 리턴해야 한다.
    // 다 받아먹는 순간 바로 그때 함수가 최종적으로 실행될 것이다.
    // 그럼 그 전까지는 함수를 계속 호출해야 한다.
    // 여기서 호출해서 exectuteFunc이 실행되면
    // 위에서 args = args.concat(arg);이 실행되어 배열에 추가된다.
    
    if (args.length < funcLength ) {
      return executeFunc;
    }
    
    // 이 아래는 else 부분이다. 즉
    // args.length >= funcLength 일때
    // 최종적으로 args의 인자들을 끌어모아서
    // 콜백함수를 실행해줘야 한다.
    
    return func.apply(this, args)
    // or return func(...args);  
}  


final code

function _curry(func) {

  const funcLength = func.length;
  let args = [];

  return function executeFunc(...arg) {
    if (!arg) {
      return func();
    }

    args = args.concat(arg);

    if (args.length < funcLength ) {
      return executeFunc;
    }

    return func.apply(this, args);
  }
}

const fun = (a, b, c) => [a, b, c];
console.log(_curry(fun)(1, 2)(3, 4)); // [1, 2, 3]

const fun2 = (a, b, c, d, e) => a + b + c + d + e;
console.log(_curry(fun2)(1)(2)(3)(4)(5)); // 15

profile
매일 성장하는 프론트엔드 개발자
post-custom-banner

0개의 댓글