함수형 프로그래밍의 Currying

sehan·2020년 4월 14일
3

Currying

connect( )( ) ... // react-redux의 connect 또한 currying을 이용한 것이다.

Currying은 여러 개의 인자를 받는 함수를 단일 인자를 받는 함수의 체인을 이용하는 방식으로 바꾸는 것을 의미한다.

https://seungdols.tistory.com/750 를 읽어보면 여러 개의 인자를 받는 함수를 일부만 받아 적용할 수 있게 만드는 것이 커링이라고 하는데 개인적인 생각으로는 이는 Partial application와 같은 의미라고 생각된다.

partial application은 기존 함수의 매개변수들 중 일부를 미리 넣어둔 새로운 함수를 만드는 것입니다. 만들어진 partial application 함수는 다음 번 호출 시에는 결과를 반환해야 합니다.

https://www.zerocho.com/category/JavaScript/post/579236d08241b6f43951af18
제로초 블로그를 참고하면 partial application은 다음 번에 함수 호출 시 결과를 반환해야 한다는데 currying의 부분 적용도 결국 일부 인자를 고정하는 것이기 때문이다.

수정: Partial은 인자의 갯수를 생각해서 계속 함수를 반환하는 것이 아니라 딱 한번만 반환하기 때문에 무조건 다음 번 호출에 인자를 모두 넣어주어야 하지만 Currying은 currying 당시 인자를 받아서 고정시킨다음 인자를 충족하지 않았다면 인자가 충족될 때까지 함수를 반환하는 방법으로 인자를 하나씩 차례대로 넣을 수 있다는 차이점이 존재한다. 그러므로 Currying의 부분적용과 Partial Application은 다르다고 볼 수 있다.

간단한 중첩 구조의 Currying

const sum = function(x) {
  return function(y) {
    return x + y;
  }
}

console.log(sum(2)(5)); // 7

위에서 볼 수 있듯이 단일 인자를 받는 함수들을 연결시킨다.
하지만 인자의 수가 많으면 많아질수록 반환해야하는 함수의 수도 많아질것이다.

(마치 콜백지옥처럼..)

따라서 이 과정을 범용화 할 수 있는 방법을 알아보도록 하자.

Currying 범용화

함수가 필요로 하는 인자 갯수에 도달하기 전까지는 함수를 반환하고 인자 갯수를 모두 충족시켰으면 비로소 값을 반환한다.

function curry(uncurried) {
  const requireLength = uncurried.length;
  const slice = Array.prototype.slice;
  
  return (function resolver(){
    let storedArgs = slice.call(arguments); // 전달된 arguments를 기억하고 있음
    
    return function() {
      let newArgs = storedArgs.slice(); // 이전에 추가된 arguments를 복사
      Array.prototype.push.apply(newArgs, arguments); // 새로 추가된 arguments를 기존의 arguments에 복사
      next = newArgs.length >= requireLength ? uncurried: resolver;
      
      return next.apply(null, newArgs); // localArgs를 arguments에 넣어 호출
    }
  }());
}

function sum(x,y,z) {
 return x + y + z; 
}

const curriedSum = curry(sum);
console.log(curriedSum(1)(2)(3)); // 6

위 코드의 작동방식은 curring하고자 하는 함수의 인자가 모두 채워질 때까지 클로저를 통해 기억했던 이전의 arguments들과 추가된 arguments를 합치고 함수의 인자가 모두 충족되면 curry의 인자로 넘겨준 함수를 arguments와 함께 호출한다.

ES6 전개 연산자로 더 쉽게 범용화하기

function curry(fn, ...fixedArgs) {
  return (...newArgs) => fn(...fixedArgs, ...newArgs);
}

function volume(l,h,w) {
    return l * h * w
}

const hCy = curry(volume,100);
hCy(200,900); // 18000000l
hCy(70,60); // 420000l

Currying의 부분적용

커링의 부분적용은 여러 인자를 한번에 받아 고정시키는 것을 의미한다.
이는 함수를 호출할 때 매개변수가 항상 비슷하다면 고려해볼만 하다.

function multiple(x,y) {
  return x*y;
}

function curry(fn) {
  const slice = Array.prototype.slice;
  const storedArgs = slice.call(arguments, 1);
  
  return function() {
   const newArgs = slice.call(arguments);
    return fn.apply(null, storedArgs.concat(newArgs));
  }
}

const curriedMultiple = curry(multile, 2);
curriedMultiple(5); // 10

Currying을 사용하는 이유

Currying을 사용하는 이유에 대한 정확한 자료를 찾기가 힘들어서 일단 장점이라고 생각한 것들을 어느정도 정리해보았다.

1. 함수의 확장이 쉽다.

예를 들어 곱하는 기능을 담당하는 함수가 존재한다고 가정하고 2배를 곱하는 함수와 3배를 곱하는 함수가 필요하다고 할 때 우리는 곱하는 기능을 담당하는 함수를 확장할 수 있다.

currying의 부분 적용을 이용하는 방법도 있고 2나 3을 인자로 넣은 커링된 함수를 변수에 담고 입맛에 맞게 인자를 넣는 방법도 있다.

function multiple(x,y) {
  return x*y;
}

function curry(uncurried) {
  const requireLength = uncurried.length;
  const slice = Array.prototype.slice;
  
  return (function resolver(){
    let storedArgs = slice.call(arguments); // 전달된 arguments를 기억하고 있음
    
    return function() {
      let newArgs = storedArgs.slice(); // 이전에 추가된 arguments를 복사
      Array.prototype.push.apply(newArgs, arguments); // 새로 추가된 arguments를 기존의 arguments에 복사
      next = newArgs.length >= requireLength ? uncurried: resolver;
      
      return next.apply(null, newArgs); // localArgs를 arguments에 넣어 호출
    }
  }());
}

const curriedMultiple = curry(multiple);

const doubleMultiple = curriedMultiple(2); 
const tripleMultiple = curriedMultiple(3);

console.log(doubleMultiple(4)); // 8
console.log(tripleMultiple(6)); // 18

2. 중복 인자를 피할 수 있다.

volume(200,30,100) // 2003000l
volume(32,45,100); //144000l
volume(2322,232,100) // 53870400l

위의 100과 같이 중복되는 인자가 존재하는 경우

function volume(h) {
    return (w) => {
        return (l) => {
            return l * w * h
        }
    }
}

const hCylinderHeight = volume(100);
hCylinderHeight(200)(30); // 600,000l
hCylinderHeight(2322)(232); // 53,870,400l

currying으로 중복 인자를 해결할 수 있다.

참고 문헌: dev-momo 티스토리, edykim, ZeroCho, Seungdols, Understanding Currying in JavaScript

profile
필요한 공부를 찾아가며 현재를 열심히 살아가려 노력하고 있습니다.

0개의 댓글