커리(Curry)
순수한 함수형 프로그래밍 언어에서 함수는 불려지거나 호출된다고 표현하기 보다 적용된다고 표현한다.
다음은 함수 적용의 예다
** call은 인자를 하나하나씩 전달하지만 apply는 배열로 인자를 전달한다.
// 함수를 정의한다.
var sayHi = function(who){
return "hello" + (who ? ", " + who : "") + "!";
// who가 true일 경우에 "," false일 경우에 ""
};
// 함수를 호출한다.
console.log(sayHi()); // hello
console.log(sayHi('world')); // hello, world!
// 함수를 적용(apply) 한다.
console.log(sayHi.apply(null, ['hello'])); // 'hello, hello!'
예제에서 보는것처럼 함수를 적용하는 것과 호출하는 것 모두 결과는 동일하다. apply()는 두개의 매개변수를 받는다. 첫번째는 이 함수 내에 this와 바인딩할 객체이고, 두번째는 배열 또는 인자(arguments)로 함수 내부에서 배열과 비슷한 형태의 arguments 객체로 사용하게 된다. 첫번째 매개변수가 null이면 this는 전역객체를 가리킨다. 즉 함수를 특정 객체의 메서드로서가 아니라 일반적인 함수로 호출할 때와 같다.
함수가 객체의 메서드일때는 null을 전달하지 않는다
var sayHi = function(who){ return "hello" + (who ? ", " + who : "") + "!";
// who가 true일 경우에 "," false일 경우에 ""
};
var alien = {
sayHi: function(who){
return 'hello' + (who ? ', ' + who : '') + '!';
}
};
console.log(alien.sayHi('world')); // 'hello, world!'
console.log(sayHi.apply(alien, ['humans'])); // 'hello, humans!'
이 코드에서 sayHi() 내부의 this는 alien을 가리킨다. 앞선 예제에서 this는 전역 객체를 가리킨다.
apply()와 더불어 Function.prototype 객체에 call() 메서드도 있다는 것을 알아둬여한다. call() 메서드 역시 apply()와 매우 비슷하다.
함수의 매개변수가 단 하나일때는 굳이 배열을 만들지 않고 요소 하나만 지정하는 방법이 더 편하기 때문에 call()을 쓰는게 더 나을 때도 있다.
var sayHi = function(who){
return "hello" + (who ? ", " + who : "") + "!";
// who가 true일 경우에 "," false일 경우에 ""
};
var alien = {
sayHi: function(who){
return 'hello' + (who ? ', ' + who : '') + '!';
}
};
// 배열을 만들지 않는 두 번째 방법이 더 효과적
console.log(sayHi.apply(alien, ['humans'])); // 'hello, humans!'
console.log(sayHi.call(alien, 'humans')); // 'hello, humans!'
부분적인 적용
인자 전부가 아니라 일부 인자만 전달하는 방법에 대해 설명한다.
다음 코드는 가상의 partialApply() 메서드 사용법을 보여준다.
var add = function(x, y){
return x + y;
};
// 모든 인자를 적용한다.
console.log(add.apply(null, [5, 4])); // 9
// 인자를 부분적으로만 적용한다.
var newadd = add.partialApply(null, [5]);
// 새로운 함수에 인자를 적용
newadd.apply(null, [4]); // 9
커링(Curring)
커링은 함수를 변형하는 과정이다. 자바스크립트에서는 add() 함수를 수정하여 부분 적용을 처리하는 커링 함수로 만들 수 있다.
// 커링된 add()
// 부분적인 인자의 목록을 받는다.
function add(x, y){
if(typeof y === 'undefined'){ // 부분적인 적용
return function(y){
return x + y;
};
}
// 전체 인자를 적용
return x + y;
}
// 테스트
console.log(typeof add(5)); // function
console.log(add(3)(4));
// 새로운 함수를 만들어 저장
var add2000 = add(2000);
console.log(add2000(10)); // 2010
다음 예제는 이를 수행하는 범용 함수를 보여준다.
function schonfinkelize(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);
}
}
- Array.prototype 에서 slice()메서드를 빌려와서 arguments를 배열로 바꿔 사용함
- 지역변수 slice에 slize()메서드에 대한 참조를 저장하고 stored_args에 인자를 저장함
- 이때 첫번째 인자는 커링될 함수이기 때문에 떼어냄
- 새로 반환된 함수는 클로저를 통해 비공개로 저장해둔 stored_args와 slice 참조에 접근
- 새로운 함수는 이미 일부 적용된 인자인 stored_args와 새로운 인자 new_args를 합친뒤 클로저에 저장되어 있는 fn에 적용
테스트
// 일반함수
function add(x, y){
return x + y;
}
// 함수를 커링하여 새로운 함수를 얻는다.
var newadd = schonfinkelize(add, 5);
console.log(newadd(4)); // 9
console.log(schonfinkelize(add, 6)(7)); // 13
function add2(a, b, c, d, e){
return a + b + c + d + e;
}
// 여러개의 인자를 사용할 수도 있다.
console.log(schonfinkelize(add2, 1, 2, 3)(5, 5)); // 16
// 2단계 커링
var addOne = schonfinkelize(add2, 1);
console.log(addOne(10, 10, 10, 10)); // 41
var addSix = schonfinkelize(addOne, 2, 3);
console.log(addSix(5, 5)); // 16
커링을 사용해야 할 경우
어떤 함수를 호출할 때 대부분의 매개변수가 항상 비슷하다면, 커링의 적합한 후보라고 할 수 있다.