Partial Application in JavaScript

이글은 John Resig님의 'Partial Application in JavaScript'를 번역한 것이다. curry 함수를 JavaScript에서 구현하는 방법을 다룬다.

Partial Application in JavaScript

함수를 부분적으로 apply하는 방법은 굉장히 중요하다. 이 기법은 함수가 실행되기 전에 인자들 중에서 몇 개를 미리 결정하는 것을 말하고 사실상 부분적으로 apply하는 함수는 호출 가능한 새 함수를 반환한다. 이 말의 예를 코드로 만들면 다음과 같다:

String.prototype.csv = String.prototype.split.partial(/,\s*/);

var results = "John, Resig, Boston".csv();

alert( (results[1] == "Resig") + " The text values were split properly" );

partial()은 함수를 반환하는 데, 정규표현식이 인자로 미리 채워진 .split() 메소드를 반환한다. 반환된 함수 .csv()는 comma-separated values를 list로 변환하는 함수다. 함수의 인자 몇 개를 순서대로 채워서 함수를 만드는(반환하는 것) 것을 보통 curry라고 부른다. 어떻게 curry하는지 Prototype 라이브러리에 구현된 것을 살펴보자(역주 - 보통 이런 함수를 curry function이라고 부른다).

Function.prototype.curry = function() {
  var fn = this, args = Array.prototype.slice.call(arguments);
  return function() {
    return fn.apply(this, args.concat(
      Array.prototype.slice.call(arguments)));
  };
};

이 예제는 클로저로 상태를 저장하는 법을 잘 설명한다. 인자를 저장해 두고 새로 만든 함수로 넘긴다. 저장된 인자와 새로운 함수의 인자는 합쳐져서 원래의 함수로 넘겨진다. 그래서 기존의 함수에서 인자가 몇 개 채워진 새로운 함수를 사용할 수 있다.

이제 부분적으로 apply하는 기법을 개선하여 완벽하게 만들어보자. 단순히 처음 몇 개의 인자를 저장해두는 것이 아니라 위치에 상관없이 동작하도록 할 수 있다. 부분적으로 apply하는 기법은 다른 언어에 있던 아이디어이지만 Oliver Steele는 자신의 Functional.js에서 구현했고 그가 JavaScript에서 처음으로 구현한 사람중 하나다. 다음의 예를 살펴보자:

Function.prototype.partial = function(){
  var fn = this, args = Array.prototype.slice.call(arguments);
  return function(){
    var arg = 0;
    for ( var i = 0; i < args.length && arg < arguments.length; i++ )
      if ( args[i] === undefined )
        args[i] = arguments[arg++];
    return fn.apply(this, args);
  };
};

이 함수는 .curry()와 매우 유사하지만 차이가 있다. 먼저 partial()함 수를 호출할 때 나중으로 미룰 인자에는 undefined라고 명시하도록 하고 인자를 합치는 기교를 부려야 한다. 넘겨진 인자들을에 대해 loop를 돌면서 undefined로 명시한 것을 찾아 합친다.

String을 split하는 예제는 이미 위에서 살펴봤으니 이 함수가 사용될 만한 다른 것에도 적용해보자. 쉽게 delay시킬 수 있는 함수를 만든다:

var delay = setTimeout.partial(undefined, 10);

delay(function(){
  alert( "A call to this function will be temporarily delayed." );
});

새로 만든 delay()함수에 다른 함수를 인자로 넘기면 10밀리초 후에 인자로 넘긴 함수가 호출될 것이다.

event를 바인딩하는 데에도 적용해 보자:

var bindClick = document.body.addEventListener
  .partial("click", undefined, false);

bindClick(function(){
  alert( "Click event bound via curried function." );
});

이 기법은 보통 라이브러리에 들어 있는 이벤트를 바인딩하는 helper 메소드를 만들 때에도 유용하다. 그래서 사용자가 이 helper 함수를 사용하여 불필요한 인자로 귀찮아 지는 일을 줄일 수 있다.

끝으로 우리는 클로저를 사용하여 복잡한 함수를 쉽게 단순화 시켰다. 이 것은 함수형 언어인 JavaScript의 힘을 보여주는 한 예일 뿐이다.

이 것은 내가 쓰고 있는 책 Secrets of the JavaScript Ninja에 포함된다. 이 책은 2008년 가을에 출간할 예정이다(역주 - 출판사 manning에 따르면 현재 2012년 3월에 출간될 예정이다).