/ javascript

[번역] 초보자를 위한 함수형 자바스크립트 Currying 가이드


커링 currying, 또는 partial application은 기존 자바스크립트 코딩에 익숙한 사람들에게 혼란을 주는 기술 중 하나이다. 하지만 커링을 적용하면 자바스크립트를 훨씬 더 읽기 쉽게 만들 수 있다.

더 읽기 쉽고 유연하게

함수형 자바스크립트의 강점은 코드를 짧고 간결하게, 반복을 최소화하여 구현한다는 것이다. 함수형 프로그래밍이 익숙하지 않다면 이해하기 어려울 지도 모른다.

Currying은 무엇인가

Currying 은 1967년 Christopher Strachey 가 Haskell Brooks Curry의 이름에서 착안한 것이다. Currying은 여러 개의 인자를 가진 함수를 호출 할 경우, 파라미터의 수보다 적은 수의 파라미터를 인자로 받으면 누락된 파라미터를 인자로 받는 기법을 말한다. 즉 커링은 함수 하나가 n개의 인자를 받는 과정을 n개의 함수로 각각의 인자를 받도록 하는 것이다. 부분적으로 적용된 함수를 체인으로 계속 생성해 결과적으로 값을 처리하도록 하는 것이 그 본질이다.

Haskell 및 Scala와 같은 언어는 currying이 기본적으로 내장되어 있지만, 자바스크립트는 커링이 내장되어 있지 않다. 하지만 자바스크립트도 커링을 구현할 수 있다.

누군가에게 인사하는 함수를 만들어보자.

var greet = function(greeting, name) {
  console.log(greeting + ", " + name);
};
greet("Hello", "Heidi"); //"Hello, Heidi"

이 함수가 제대로 작동하려면 greetingname을 인자로 전달해야한다. currying을 중첨해서 함수를 만들 수 있다.

첫번째 커링함수

위에 만든 함수를 수정해 greeting(인사말)만을 인자로 받고, name(이름)을 받는 또 다른 함수를 반환하도록 만들어보자.

var greetCurried = function(greeting) {
  return function(name) {
    console.log(greeting + ", " + name);
  };
};

이제 greeting 에 대한 새 함수를 만들어, 인사할 사람이름을 name의 파라미터에 넣자.

var greetHello = greetCurried("Hello");
greetHello("Heidi"); //"Hello, Heidi"
greetHello("Eddie"); //"Hello, Eddie"

두 파라미터를 바꿔 함수를 호출할 수 있다.

greetCurried("Hi there")("Howard"); //"Hi there, Howard"

모두 커링하기

앞에 예제를 통해 보았듯이 원하는 만큼 많은 인자(arguments)를 사용해 함수를 만들어 볼 수 있을 것이다.

var greetDeeplyCurried = function(greeting) {
  return function(separator) {
    return function(emphasis) {
      return function(name) {
        console.log(greeting + separator + name + emphasis);
      };
    };
  };
};

위 예제에서 네 개의 인자를 사용했다. 중첩된 횟수와 상관없이 name 파라미터를 바꿔 새 함수를 만들어 보자.

var greetAwkwardly = greetDeeplyCurried("Hello")("...")("?");
greetAwkwardly("Heidi"); //"Hello...Heidi?"
greetAwkwardly("Eddie"); //"Hello...Eddie?"

curried 함수의 ()안 파라미터들을 바꿔 함수를 커스터마이징할 수 있다.

var sayHello = greetDeeplyCurried("Hello")(", ");
sayHello(".")("Heidi"); //"Hello, Heidi."
sayHello(".")("Eddie"); //"Hello, Eddie."
var askHello = sayHello("?");
askHello("Heidi"); //"Hello, Heidi?"
askHello("Eddie"); //"Hello, Eddie?"

Currying 함수 만들기

이처럼 커링은 어떤 함수를 호출할 때 대부분의 매개 변수가 항상 비슷할 때 유용하게 쓸 수 있다. 매개변수 일부를 적용하여 새로운 함수를 동적으로 생성하면 이 동적 생성된 함수는 반복적으로 사용되는 매개변수를 내부적으로 저장하여, 매번 인자를 전달하지 않아도 원본함수가 기대하는 기능을 채워 놓는다.

그러나 문제는 복잡한 구문이다. 커링 함수를 만들 때는 함수를 중첩하고, 인자를 ()로 분리하고, 새 함수를 호출할 수 있게 만들어야 하는데, 이 때문에 코드가 지저분하게 된다.

따라서 함수의 이름과 인자만 가지고 사용할 수 있는 currying 함수를 만들어 사용하면 된다.. currying 함수는 사용되는 함수의 인자 목록을 추출해, 원본 함수에 커링한다.

var curryIt = function(uncurried) {
  var parameters = Array.prototype.slice.call(arguments, 1);
  return function() {
    return uncurried.apply(this, parameters.concat(
      Array.prototype.slice.call(arguments, 0)
    ));
  };
};

첫 번째 할 일은 지금까지 입력 받은 모든 인자를 복사하는 것이다. Array#slice 메소드를 이용해 ,arguments의 사본을 parameters라는 변수에 저장한다.
그리고 또한 이 추가되는 인자를 parameters에 다시 저장해야 한다.

curryIt() 함수 사용을 위해서는 () 내 함수 이름과 인자를 차례로 넣어 선언해야한다. 빠진 인자는 재 선언하여 출력할 수 있다.

var greeter = function(greeting, separator, emphasis, name) {
  console.log(greeting + separator + name + emphasis);
};
var greetHello = curryIt(greeter, "Hello", ", ", ".");
greetHello("Heidi"); //"Hello, Heidi."
greetHello("Eddie"); //"Hello, Eddie."

원본 curring 함수에서 파생된 함수를 만들 때 인자의 사용은 자유롭다.

var greetGoodbye = curryIt(greeter, "Goodbye", ", ");
greetGoodbye(".", "Joe"); //"Goodbye, Joe."

Currying에 대한 고찰

작은 currying 함수는 누락되거나 선택적 매개 변수와 같은 모든 엣지 경우를 처리하지 못할 수도 있겠지만, 인자를 전달하는 구문은 매우 엄격함으로 수행하는데 문제가 없다. Ramda와 같은 일부 함수형 자바스크립트 라이브러리에는 함수에 필요한 매개 변수를 구분할 수있는 보다 유연한 커링 함수들이 있으며, 개별적으로 또는 그룹으로 전달하여 변형된 커링 함수를 만들 수 있다. currying을 광범위하게 사용하려면 라이브러리를 사용하는 것이 좋다. 커링 함수에 일관된 네이밍 규칙을 적용하면, 코드 가독성이 높아진다. 함수에서 파생된 또다른 함수들은 작동방식이 매우 명확해야하며, 어떤 인자가 들어오는지 알 수 있어야 한다.

인자 순서

커링 함수에서 인자의 순서는 매우 중요하다. 앞에 있는 인자일 수록 변동가능성이 적다. 반대로 뒤에 있는 인자일수록 변동가능성이 높다. 때문에 인자 순서를 고려해 코드를 설계하자.

결론

Currying은 함수형 자바스크립트에서 매우 유용한 기술이다. 일관성있고 사용하기 쉬운 코드는 이해도도 높기 때문에 커링함수로 작은 라이브러리도 구현해 볼 수 있다. 전체 코드에 currying을 도입함으로 함수 인자 네이밍과 처리하는 방법에 많은 도움이 될 것이다.

추천 읽기 자료

JavaScript에서 커링 currying 함수 작성하기 - Origin. Currying in JavaScript - Kevin Ennis