2016년 12월 15일 목요일

[javascript patterns][커리(Curry)] 스터디 16

커링(Curring)

function add(x, y) {
  var oldx = x, oldy = y;
  if (typeof oldy === 'undefined') { // 부분적인 적용
    return function (newy) {
      return oldx = newy;
    }
  }
  // 전체 인자를 적용
  return x + y;
}

//테스트
typeof add(5); // "function"
add(3)(4); // 7

// 새로운 함수를 만들어 저장
var add200 = add(200);
add200(10); // 2010
교재의 예제를 그대로 썼다. add가 반환하는 내부 함수에 클로저를 만든다. 클로저는 x,y의 값을 oldx와 oldy에 저장한다. 하지만 더 간단하게 할 수 있다. 다음 예제를 보자.
function add(x, y) {
  if (typeof y === "undefined") {
    return function (y) {
      return x + y;
    }
  }
  // 전체 인자를 적용
  return x + y;
}
여기에는 oldx, oldy가 없는데 원래 x는 암묵적으로 클로저에 저장되어 있고, y는 재사용을 하고 있다.

조금더 범용적인 방식으로 처리할 수 있을까?

어떤 함수라도 부분적인 매개변수를 받는 새로운 함수로 변형할 수 있을까?
function flexible_curry(fn) {
  var slice = Array.prototype.slice,
      stored_args = slice.call(arguments, 1);

  return function () {
    var new_args = slice.call(arguments),
        args = stored_args.concat(new_args);
    return fn.apply(null, args);
  };
}
되게 복잡해 보이는 이유는 단시 arguments가 배열이 아니기 때문이다. slice를 쓰고 싶은데 arguments는 배열처럼 보일 뿐 배열이 아니기 때문에 var slice = Array.prototype.slice로 slice안에 메소드를 넣었다. 그리고는 slice.call(arguments, 1); argument를 배열의 메소드로 배열처럼 사용하는 것이다. 이해가 안된다면
[1, 2, 3, 4, 5].slice(1); // [2, 3, 4, 5]
Array.prototpye.slice.call([1, 2, 3, 4, 5], 1);
이제 테스트를 해보자
function add(x, y) {
  return x + y;
}
function flexible_curry(fn) {
  var slice = Array.prototype.slice,
      stored_args = slice.call(arguments, 1);

  return function () {
    var new_args = slice.call(arguments),
        args = stored_args.concat(new_args);
    return fn.apply(null, args);
  };
}


var newadd = flexible_curry(add, 5);
newadd(5); // 10
add(fn)는 클로저로서 값이 휘발성으로 사라지지 않는다. (객체화도 되지 않았는데 어떻게?) 그것이 클로저다. 나중에 좀 더 공부해보도록 하자. 어쨋든 argument에서 첫번째 것만을 잘라내서(slice) sotred_args에 넣기 위해 Array.prototype.slice.call(arguments, 1)을 사용하였다.
그리고 반쪽자리 add(fn)를 반환한다. 그것을 newadd에 넣었고 newadd(5)를 실행하면 더하기를 수행 할 것이다. 여기서 concat은 배열끼리의 접합을 수행한 후, args에 넣는다. 그 후 fn.apply(null, args); 를 실행한다. (apply는 call과 비슷하게 동작하지만 배열을 받는 다는 것을 기억하라. 이 예제에서 apply는 아주 잘 맞는다.)
function multifly(a, b, c, d, e) { return a * b * c * d * e; }

function flexible_curry(fn) {
  var slice = Array.prototype.slice,
      stored_args = slice.call(arguments, 1);

  return function () {
    var new_args = slice.call(arguments),
        args = stored_args.concat(new_args);
    return fn.apply(null, args);
  };
}

flexible_curry(multifly, 1, 2, 3)(5, 5);


var a = flexible_curry(multifly, 1.534);
a(10, 10, 10, 10);
var b = flexible_curry(a, 2, 3);
b(6, 7);
커링은 엄청난 자유를 준다.

커링을 사용해야 할 경우

어떤 함수를 호출할 때 대부분의 매개변수가 항상 비슷하다면, 커링의 적합한 후보라고 할 수 있다. 매개 변수 일부를 적용하여 새로운 함수를 동적으로 생성하면 이 함수를 반복되는 매개변수를 내부적으로 젖아하여, 매번 인자를 전달하지 않아도 원본 함수가 기대하는 전체 목록을 미리 채워놓을 것이다.

댓글 없음:

댓글 쓰기